Первоначальная версия VidConf
This commit is contained in:
791
design/DESIGN_SYSTEM.md
Normal file
791
design/DESIGN_SYSTEM.md
Normal file
@@ -0,0 +1,791 @@
|
||||
# VidConf — Дизайн-система
|
||||
|
||||
Дизайн зафиксирован как обязательный референс для реализации; изменения —
|
||||
только через обновление этого документа и макетов `design/mockups/`. Оболочка
|
||||
поддерживает светлую и тёмную тему с переключателем и автодетектом системной
|
||||
темы (см. §0.1 и §1.2b ниже).
|
||||
|
||||
Референсы: `referens.png` (стиль/layout светлой оболочки), `collor_ico.png` (цвета/иконография тёмной комнаты конференции).
|
||||
Все hex-значения взяты пипеткой непосредственно из референсов (см. таблицы ниже)
|
||||
и проверены на контраст по формуле WCAG 2.1 (относительная светимость sRGB);
|
||||
тёмная оболочка (§1.2b) — производная от уже утверждённой палитры комнаты
|
||||
(hex переиспользованы 1:1, без повторной пипетки, см. таблицу происхождения).
|
||||
|
||||
## 0. Принцип тем
|
||||
|
||||
Оболочка (auth, лобби, календарь, «Мои конференции», join, админка, профиль,
|
||||
email) имеет **две** темы — светлую (дефолт) и тёмную, с ручным переключателем
|
||||
и автодетектом системной темы (см. §0.1). Комната конференции — **всегда**
|
||||
тёмная, отдельная тема без переключателя.
|
||||
|
||||
| | Экраны | Референс | Характер |
|
||||
|---|---|---|---|
|
||||
| **Оболочка, светлая** | auth, лобби (хаб действий), календарь (менеджер конференций), «Мои конференции», подключение по ссылке/номеру, админка, профиль, email | `referens.png` | светлый фон, глубокий индиго в типографике, крупные скругления (20–32px), pill-кнопки с обводкой, пастельно-мятный CTA, glass-панели |
|
||||
| **Оболочка, тёмная** | те же экраны оболочки | `design/mockups/dark/` (утв. 2026-07-18), палитра — производная от `collor_ico.png` (см. §1.2b) | графитовый фон, пастельная мята вместо индиго (бренд/заголовки/ссылки/CTA), розовый danger, янтарный warning/focus, те же формы/скругления/типографика, что у светлой оболочки — меняется только цвет |
|
||||
| **Комната (всегда тёмная)** | room (конференция) | `collor_ico.png` | графитовый фон, тёмно-серые плитки участников, зелёный/красный микрофон, жёлто-оранжевая рамка активного спикера, контурные иконки тулбара |
|
||||
|
||||
Темы не смешиваются на одном экране: комната конференции — единственный экран
|
||||
с собственной несменяемой тёмной темой; всё остальное — оболочка, которая
|
||||
целиком переключается между светлым и тёмным вариантом (пользователь никогда
|
||||
не видит светлую панель внутри тёмной оболочки или наоборот).
|
||||
|
||||
### 0.1 Механизм переключения (оболочка)
|
||||
|
||||
Контракт для frontend-dev (реализовано в `design/tokens.css`):
|
||||
|
||||
- **Атрибут на корне** — `data-theme="dark"` / `data-theme="light"` на
|
||||
`<html>` (или другом предке `:root`-скоупа приложения) — явный выбор
|
||||
пользователя, сохраняется (localStorage/настройка профиля), имеет приоритет
|
||||
над системной темой.
|
||||
- **Дефолт по ОС** — если `data-theme` не проставлен вообще, действует
|
||||
`@media (prefers-color-scheme: dark)`: тёмная палитра активируется
|
||||
автоматически при тёмной системной теме, светлая — при светлой/отсутствии
|
||||
поддержки. Как только пользователь явно переключил тему, автодетект
|
||||
отключается для этого выбора (селектор `:root:not([data-theme])` в
|
||||
`tokens.css` перестаёт совпадать).
|
||||
- Оба триггера переопределяют **один и тот же набор** переменных, что и
|
||||
светлый `:root` (`--color-bg`, `--color-ink-900`, `--color-accent` и т.д.) —
|
||||
весь остальной CSS оболочки (компоненты, `frontend/`) ссылается на них через
|
||||
`var()` и не нуждается в собственной `dark:`-логике.
|
||||
- `[data-theme="room"]` (комната) — независимый третий скоуп с отдельным
|
||||
неймспейсом `--color-room-*`; не активируется ни атрибутом `dark`, ни
|
||||
`prefers-color-scheme`, и не участвует в переключателе оболочки.
|
||||
|
||||
Предустановленных переговорных комнат нет — конференции создаются динамически
|
||||
(мгновенно из лобби или планово из календаря), закрепляются с повторением и
|
||||
видны в «Мои конференции»; вход — по ссылке или номеру, гости входят через
|
||||
«представиться». Макеты:
|
||||
`design/mockups/lobby.html`, `calendar.html`, `my-conferences.html`, `join.html`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Цветовые токены
|
||||
|
||||
Все значения продублированы 1:1 в `tokens.css`. Контраст текста проверен и
|
||||
везде ≥ 4.5:1 (см. колонку «контраст»), кроме специально помеченных
|
||||
декоративных/крупных элементов (правило WCAG для large text ≥ 3:1, отмечено ⚠︎).
|
||||
|
||||
### 1.1 Светлая тема (оболочка)
|
||||
|
||||
| Токен | Hex | Назначение | Контраст |
|
||||
|---|---|---|---|
|
||||
| `--color-bg` | `#F1F1F1` | фон страницы (пипетка referens.png, фон вокруг карточек) | — |
|
||||
| `--color-bg-alt` | `#F7F7F8` | альтернативный фон секций / подложка полей | — |
|
||||
| `--color-surface` | `#FFFFFF` | карточки, модалки, поля ввода | — |
|
||||
| `--color-surface-glass` | `rgba(255,255,255,.55)` | glass-панели (стат-карточки в стиле referens.png), с `backdrop-filter: blur(16px)` | — |
|
||||
| `--color-border` | `#E1E3E9` | базовая обводка карточек/инпутов | — |
|
||||
| `--color-border-strong` | `#C7CCDC` | обводка при hover | — |
|
||||
| `--color-border-pill` | `#4A5A9E` | обводка pill-кнопок (пипетка контура «НАПРАВЛЕНИЕ») | — |
|
||||
| `--color-ink-900` | `#2E3454` | основной текст (параграфы), **текст на CTA** (`--color-accent-text`, см. ниже) | 11.3:1 на `--color-bg-alt` |
|
||||
| `--color-ink-700` | `#3B4D95` | **брендовый индиго** — дисплейные заголовки, ссылки (пипетка «X'FLOW GYM») | 6.9:1 на `--color-bg` |
|
||||
| `--color-ink-500` | `#535F94` | вторичный текст (подписи, мета) | 5.7:1 на `--color-bg-alt` |
|
||||
| `--color-ink-400` | `#6976AC` | third-tier — только крупный/жирный текст (эйбрау-подписи в [скобках], ≥ 20px) | 4.1:1 ⚠︎ large-text-only |
|
||||
| `--color-ink-300` | `#A6AECB` | плейсхолдеры, disabled-текст | — |
|
||||
| `--color-accent` | `#D4F2E3` | **пастельная мята CTA** (заливка primary-кнопки; токен переименован из `--color-accent-green`, т.к. акцент больше не обязан быть зелёным) | — |
|
||||
| `--color-accent-hover` | `#C7EBD9` | hover CTA (переименовано из `--color-accent-green-hover`; осветлена на 50%, было `#8FD6B2`) | — |
|
||||
| `--color-accent-active` | `#BAE4CE` | active/pressed CTA (переименовано из `--color-accent-green-active`; осветлена на 50%, было `#74C89D`) | — |
|
||||
| `--color-accent-text` | `#2E3454` | текст на мятной заливке — тёмный индиго, равен `--color-ink-900` (переименовано из `--color-accent-green-text`) | 10.15:1 (default) / 9.40:1 (hover) / 8.69:1 (active) |
|
||||
| `--color-success` | `#0A7A4C` | текст статуса «свободна» | 5.38:1 на белом |
|
||||
| `--color-success-bg` | `#E4F7ED` | подложка бейджа «свободна» | — |
|
||||
| `--color-success-dot` | `#46916B` | индикатор-точка (пастелизирована, была неоновой `#1BC46B`; не единственный носитель статуса) | 3.80:1 на белом (нетекст) |
|
||||
| `--color-danger` | `#C7364F` | текст статуса «отменена», кнопки удаления | 5.16:1 на белом |
|
||||
| `--color-danger-bg` | `#FCE7EB` | подложка бейджа «отменена» | — |
|
||||
| `--color-danger-dot` | `#C65366` | индикатор-точка (пастелизирована, была `#E0526B`) | 4.35:1 на белом (нетекст) |
|
||||
| `--color-warning` | `#7F560D` | текст статуса «запланирована» | 6.48:1 на белом |
|
||||
| `--color-warning-bg` | `#FBF0DC` | подложка бейджа «запланирована» | — |
|
||||
| `--color-warning-dot` | `#B58A26` | индикатор-точка (углублена с `#D6A83D` ради контраста 3:1 на белом; рамка спикера в тёмной теме — отдельный токен, тот же исходный тон, оставлена без изменений) | 3.17:1 на белом (нетекст) |
|
||||
| `--color-focus-ring` | `#3B4D95` | focus-visible обводка | — |
|
||||
|
||||
### 1.2a Тёмная тема — комната конференции
|
||||
|
||||
| Токен | Hex | Назначение | Контраст |
|
||||
|---|---|---|---|
|
||||
| `--color-room-bg` | `#1E1E1E` | фон канвы конференции (пипетка collor_ico.png) | — |
|
||||
| `--color-room-surface` | `#2A2A2A` | топбар, нижний тулбар, всплывающие панели | — |
|
||||
| `--color-room-surface-raised` | `#333333` | дропдауны, поповеры, панель чата | — |
|
||||
| `--color-room-tile` | `#363636` | плитка участника (пипетка тёмно-серых плиток) | — |
|
||||
| `--color-room-tile-border` | `#3F3F3F` | обводка плитки в покое | — |
|
||||
| `--color-room-tile-hover` | `#3D3D3D` | плитка при hover/фокусе | — |
|
||||
| `--color-room-text-primary` | `#E8E8E8` | имя участника, основной текст | 9.9:1 на tile |
|
||||
| `--color-room-text-secondary` | `#B0B0B0` | вторичный текст (время, статус) | 5.6:1 на tile |
|
||||
| `--color-room-text-tertiary` | `#7A7A7A` | таймстемпы чата (декоративно, не единственный носитель смысла) | — |
|
||||
| `--color-room-mic-on` | `#7FDDA8` | **микрофон активен**, пастельная мята (была неоновая `#46D97A`, тот же зелёный вектор, что и `--color-accent` в оболочке) | 8.76:1 на surface / 7.37:1 на tile (нетекст) |
|
||||
| `--color-room-mic-off` | `#EB93A1` | **микрофон выключен / mute**, пастельный розово-красный (была `#F2778C`) | 6.30:1 на surface / 5.31:1 на tile (нетекст) |
|
||||
| `--color-room-speaker-ring` | `#D6A83D` | **рамка активного спикера**, жёлто-оранжевая (пипетка collor_ico.png) | 6.51:1 ⚠︎ декоративная рамка |
|
||||
| `--color-room-danger` | `#EB93A1` | иконка «выйти», деструктивные контуры (пастелизирована вместе с mic-off) | 6.30:1 (нетекст) |
|
||||
| `--color-room-danger-bg` | `#B85468` | заливка кнопки «Выйти» (hover/подтверждение), пастельная роза (была `#B3384E`) | 4.66:1 (белый текст) |
|
||||
| `--color-room-danger-bg-hover` | `#B9495F` | hover заливки danger (была `#C24363`) | 5.02:1 (белый текст) |
|
||||
| `--color-room-focus-ring` | `#D6A83D` | focus-visible обводка в тёмной теме | — |
|
||||
|
||||
Статусы никогда не передаются только цветом: у микрофона есть форма иконки
|
||||
(перечёркнутый микрофон = mute), у бейджей лобби — иконка + текст, у активного
|
||||
спикера — рамка + подпись, у конфликта брони — иконка `AlertTriangle` + текст.
|
||||
|
||||
### 1.2b Тёмная тема — оболочка
|
||||
|
||||
Макеты: `design/mockups/dark/*.html` (6
|
||||
экранов, 1:1 разметка со светлыми прообразами `design/mockups/*.html`,
|
||||
меняется только цвет — см. `design/mockups/dark/README.md`, полное
|
||||
обоснование подбора палитры). Механизм переключения — §0.1.
|
||||
|
||||
**Принцип.** Тёмная оболочка не изобретает новых цветов, а переиспользует уже
|
||||
утверждённые токены комнаты (§1.2a) в новых ролях — так тёмное лобби и тёмная
|
||||
комната конференции читаются как одна система при переходе пользователя между
|
||||
ними. Там, где у комнаты нет готового аналога (бренд-панель, формы, glass-
|
||||
карточки — компоненты, которых в комнате не существует), значения получены
|
||||
подмешиванием уже утверждённых цветов комнаты к графитовому фону/друг к другу,
|
||||
а не придуманы независимо.
|
||||
|
||||
**Токены, переиспользованные из комнаты 1:1** (тот же hex, что и `--color-room-*`, §1.2a):
|
||||
|
||||
| Токен оболочки (`[data-theme='dark']`) | = токен комнаты | Hex | Новая роль в оболочке |
|
||||
|---|---|---|---|
|
||||
| `--color-bg` | `--color-room-bg` | `#1E1E1E` | фон страницы |
|
||||
| `--color-surface` | `--color-room-surface` | `#2A2A2A` | карточки, топбар, поля |
|
||||
| `--color-border` | `--color-room-tile-border` | `#3F3F3F` | базовая обводка |
|
||||
| `--color-ink-900` | `--color-room-text-primary` | `#E8E8E8` | основной текст |
|
||||
| `--color-ink-500` | `--color-room-text-secondary` | `#B0B0B0` | вторичный текст |
|
||||
| `--color-ink-300` | `--color-room-text-tertiary` | `#7A7A7A` | плейсхолдеры/disabled |
|
||||
| `--color-ink-700`, `--color-accent`, `--color-border-pill`, `--color-success`, `--color-success-dot` | `--color-room-mic-on` | `#7FDDA8` | мята: бренд/заголовки/ссылки/CTA/«успех» — единая пастельно-зелёная роль (см. ниже) |
|
||||
| `--color-danger`, `--color-danger-dot` | `--color-room-mic-off` | `#EB93A1` | розово-красный: текст/иконки/обводка статуса «отменена»/ошибки |
|
||||
| `--color-warning`, `--color-warning-dot` | `--color-room-speaker-ring` | `#D6A83D` | янтарь: статус «запланирована» |
|
||||
| `--color-focus-ring` | `--color-room-focus-ring` | `#D6A83D` | кольцо `:focus-visible` |
|
||||
| `--color-danger-solid`, `--color-danger-solid-hover` | `--color-room-danger-bg(-hover)` | `#B85468` / `#B9495F` | заливка **сплошных** danger-кнопок (`.btn-danger`) — отдельная пара, см. ниже почему |
|
||||
|
||||
**Одна мята — три роли.** В светлой оболочке бренд-индиго (`--color-ink-700`)
|
||||
и мятный CTA (`--color-accent`) — два разных токена/hex. В тёмной оболочке они
|
||||
**сведены к одному и тому же** `#7FDDA8` (= `--color-room-mic-on`), как и в
|
||||
самой комнате, где одна пастельная мята уже покрывает всё «позитивное»
|
||||
(микрофон вкл, демонстрация экрана, счётчик участников, отправка в чате).
|
||||
Различие ролей (бренд-текст vs интерактивная заливка) остаётся читаемым за
|
||||
счёт контекста, а не второго оттенка.
|
||||
|
||||
**Почему заливка danger-кнопок — отдельный токен.** В светлой теме один hex
|
||||
(`--color-danger`, `#C7364F`) одинаково хорошо работает и как текст на белом,
|
||||
и как заливка кнопки с белым текстом. В тёмной теме роли расходятся:
|
||||
`--color-danger` (`#EB93A1`, светлый пастельный тон) отлично читается как
|
||||
**текст на тёмном фоне** (6.30:1 на `#2A2A2A`), но слишком светлый для белого
|
||||
текста кнопки поверх него (≈1.9:1, провал). Поэтому у **тёмной** оболочки
|
||||
заведена отдельная пара `--color-danger-solid` / `--color-danger-solid-hover`
|
||||
(`#B85468` / `#B9495F`, контраст с белым текстом 4.66:1 / 5.02:1) — только для
|
||||
сплошных danger-кнопок (`.btn-danger`: «Отменить все повторения» в календаре,
|
||||
«Удалить» в админке). В светлой теме этот токен не заведён — компонент
|
||||
`.btn-danger` там по-прежнему ссылается на `--color-danger` напрямую (§4.3),
|
||||
т.к. проблема специфична для тёмной палитры.
|
||||
|
||||
**Новые токены** (введены там, где у комнаты нет готового компонента-аналога —
|
||||
формы, бренд-панель, glass-карточки; получены смешиванием уже утверждённых
|
||||
цветов комнаты, не придуманы независимо):
|
||||
|
||||
| Токен | Hex | Как получен | Контраст | Роль |
|
||||
|---|---|---|---|---|
|
||||
| `--color-bg-alt` | `#242424` | между `--color-room-bg` и `--color-room-surface` | — | альт-фон секций, инпуты, disabled-поля |
|
||||
| `--color-surface-glass` | `rgba(51,51,51,.55)` | `--color-room-surface-raised` (`#333333`) с прозрачностью, по образцу светлой `--color-surface-glass` | — | glass-панели (стат-виджеты) |
|
||||
| `--color-border-strong` | `#585858` | осветление `--color-border` | 2.02:1 на surface (нетекст, декоративный hover-акцент, задублирован тенью — не единственный носитель состояния) | обводка при hover |
|
||||
| `--color-ink-400` | `#5D9A78` | мята `#7FDDA8` × фон `#1E1E1E`, смешение 35% | 5.05:1 на bg / 4.35:1 на surface | эйбрау-подписи `[ В СКОБКАХ ]`, крупные подписи |
|
||||
| `--color-accent-hover` | `#75CA9A` | мята × фон, смешение 10% | текст `#10331F` на заливке: 7.04:1 | hover CTA |
|
||||
| `--color-accent-active` | `#6CB78C` | мята × фон, смешение 20% | текст `#10331F` на заливке: 5.80:1 | active/pressed CTA |
|
||||
| `--color-accent-text` | `#10331F` | буквальный hex, уже встречающийся инлайн в `room.html` (`.chat-input-row button`, `.tb-btn.is-positive-active`, `.badge-count`) как цвет текста на `--color-room-mic-on` | 8.45:1 на мяте | текст на CTA/бренд-заливках мятой |
|
||||
| `--color-success-bg` | `rgba(127,221,168,.14)` | мята, низкая непрозрачность поверх тёмной поверхности | — | подложка бейджей «идёт сейчас»/«свободна»/«активен» |
|
||||
| `--color-danger-bg` | `rgba(235,147,161,.14)` | розовый (mic-off), низкая непрозрачность | — | подложка бейджей «отменена» |
|
||||
| `--color-warning-bg` | `rgba(214,168,61,.16)` | янтарь (speaker-ring), низкая непрозрачность | — | подложка бейджей «запланирована» |
|
||||
| `--shadow-sm/md/lg/glass` | `rgba(0,0,0,.35–.5)` | по образцу `--shadow-room-panel` комнаты (тёмные тени вместо индиго-тонированных) | — | тени карточек/модалок/glass-панелей |
|
||||
|
||||
**Компонентные исключения (не токены, точечные правки в самих макетах).**
|
||||
Пять мест, где CSS светлого макета использовал литеральный hex/rgba вместо
|
||||
`var()` и поэтому не мог автоматически перекраситься — задокументированы в
|
||||
`design/mockups/dark/README.md` (раздел «Точечные правки поверх скопированного
|
||||
CSS»), в `tokens.css` не заводились как отдельные токены:
|
||||
|
||||
1. Аватары/`.weekday-chip.is-selected`/`.badge-role-admin` — заливка
|
||||
`var(--color-ink-700)` + жёсткий `color: #fff` заменены на
|
||||
`var(--color-accent-text)` (белый на мятной заливке нечитаем).
|
||||
2. `.btn-danger` — заливка `var(--color-danger-solid)` вместо
|
||||
`var(--color-danger)` (см. таблицу выше).
|
||||
3. `.copy-toast` (тултип «Скопировано») — фон был `var(--color-ink-900)`
|
||||
(тёмный в светлой теме → стал бы светлым в тёмной, ломая тёмный чип-
|
||||
тултип); в макетах заменён на буквальный `#333333`
|
||||
(= `--color-room-surface-raised`). При переносе в `frontend/` (реальный
|
||||
компонент на React, не статичная копия страницы) рекомендуется завести
|
||||
токен-алиас `--color-surface-raised` под обе темы, а не повторять этот
|
||||
литерал — тултип не единственное будущее место такой потребности.
|
||||
4. Декоративные градиенты/rgba-подложки (бренд-панель `login.html`, фон
|
||||
`body` в `lobby.html`, `.day-col.is-today`/`.cancel-alert` в
|
||||
`calendar.html`, box-shadow фокуса/ошибки форм, glass-обводка) — тональные
|
||||
аналоги новой палитры (мятное свечение вместо индигового, янтарный
|
||||
focus-glow вместо индигового, розовый error-glow); та же практика уже
|
||||
используется в светлых макетах (см. `auth.html` — там эти значения тоже
|
||||
не токенизированы).
|
||||
5. `color-scheme: dark` — добавлен в активационный блок `tokens.css` (см.
|
||||
§0.1), чтобы нативные элементы форм (скроллбар, чекбоксы) тоже
|
||||
рендерились в тёмной палитре браузера.
|
||||
|
||||
**Доступность.** Текст ≥ 4.5:1 везде, кроме крупных/декоративных элементов
|
||||
(правило §5, порог 3:1). Статус не передаётся только цветом (см. §1.2a).
|
||||
Палитра пастельная, той же светлоты, что и утверждённая палитра комнаты —
|
||||
неоновых/кислотных акцентов не вводилось. Регрессия — `design/tools/contrast.py`.
|
||||
|
||||
### 1.3 Проверка контраста
|
||||
|
||||
Палитра пастельная (не неоновая) во всей системе — все текстовые пары
|
||||
проходят WCAG 2.1 (≥4.5:1, кроме отмеченных ⚠︎ декоративных/крупных
|
||||
элементов, ≥3:1). Регрессионная проверка — скрипт `design/tools/contrast.py`
|
||||
(явный список пар с ratio и порогом), запуск: `python3 design/tools/contrast.py`
|
||||
— 24/24 пар PASS.
|
||||
|
||||
### 1.4 Логотип
|
||||
|
||||
Логотип — стилизованный **объектив камеры**, отсылка к видео как сути
|
||||
продукта.
|
||||
|
||||
**Файлы:**
|
||||
- `design/logo.svg` — мастер-версия, квадратный viewBox `0 0 64 64`,
|
||||
масштабируется без искажений. Для крупных начертаний: центральный медальон
|
||||
лобби, промо-материалы, шапка письма при экспорте в PNG высокого разрешения.
|
||||
- `design/favicon.svg` — упрощённая версия для мелких размеров (16–32px):
|
||||
меньше колец, толще штрихи, чтобы силуэт не «замыливался». Используется как
|
||||
иконка в топбаре рядом со словом «VidConf» во всех макетах, а также как
|
||||
browser favicon / app-иконка продакшен-сборки.
|
||||
|
||||
**Конструкция (обе версии):**
|
||||
- центр — плоский шестигранник (`polygon`), закрытая диафрагма объектива,
|
||||
залита сплошным цветом;
|
||||
- вокруг — концентрические окружности (4 в мастер-версии, 1 в favicon),
|
||||
каждая нарисована как `<circle>` со `stroke-dasharray`/`stroke-dashoffset`,
|
||||
что даёт разорванные на сегменты дуги неровной длины и разной толщины
|
||||
штриха — техно/HUD-эстетика, ощущение вращения диафрагменных лепестков.
|
||||
|
||||
**Цвет:** строго монохром, один тон — `currentColor`, наследующий цвет
|
||||
текста в месте использования (в проекте это всегда `var(--color-ink-700)`,
|
||||
`#3B4D95`). Никаких градиентов, бликов или цветов из референса-эскиза.
|
||||
Контраст `#3B4D95` на `--color-bg` (`#F1F1F1`) — 6.93:1, на `--color-surface`
|
||||
(`#FFFFFF`) — 7.83:1 (обе пары — в `design/tools/contrast.py`, порог для
|
||||
графики 3:1 — проходят с большим запасом даже как текстовый).
|
||||
|
||||
**Использование в макетах:**
|
||||
- топбар/сайдбар (`.brand-mark`) — `favicon.svg` инлайн, `22×22px`
|
||||
(соразмерно высоте строки словесного знака, заменяет прежний `.logo-dot`)
|
||||
во всех макетах `design/mockups/*.html`, где есть слово «VidConf»;
|
||||
- центральный медальон лобби (`.hub-logo`) — полная `logo.svg`, `56×56px`
|
||||
внутри медальона `156×156px`, вместе со словом «VidConf» под знаком (см.
|
||||
§4.12 «Карточка-действие хаба (лобби)» — геометрия перекрытия медальона);
|
||||
- `email-summary.html` — инлайн SVG допустим только для HTML-макета;
|
||||
в продакшен-письме обязателен растровый PNG (почтовые клиенты в массе не
|
||||
рендерят инлайн-SVG), см. комментарий в файле рядом с разметкой.
|
||||
|
||||
### 1.5 Аватары (инициалы)
|
||||
|
||||
Круглый аватар с инициалами (`.avatar`, компонент `Avatar` §4) — заливка
|
||||
**всегда** `var(--color-ink-700)` (основной, current-user — user-pill топбара,
|
||||
сайдбар админки) либо `var(--color-ink-500)` (второстепенные, списки
|
||||
участников — таблицы админки, участники встречи в письме-саммари). Текст
|
||||
инициалов — **всегда светлый, `#FFFFFF`**, никогда не тёмный `ink-900`/
|
||||
`ink-700`: тёмные инициалы на индиго-заливке дают нечитаемый, «слипшийся»
|
||||
результат. Правило зафиксировано как регрессионные пары в
|
||||
`design/tools/contrast.py`:
|
||||
|
||||
| Заливка | Текст | Контраст |
|
||||
|---|---|---|
|
||||
| `--color-ink-700` (`#3B4D95`) | `#FFFFFF` | 7.83:1 |
|
||||
| `--color-ink-500` (`#535F94`) | `#FFFFFF` | 6.12:1 |
|
||||
|
||||
Обе пары — с большим запасом выше порога 4.5:1 для текста. Дополнительно:
|
||||
`font-weight` инициалов — не ниже 600 (используется `--text-caption`,
|
||||
`600 12px`), `text-transform: none` (кириллические инициалы не должны
|
||||
проходить через uppercase-трансформацию родителя).
|
||||
|
||||
**⚠ Предупреждение о каскаде CSS-специфичности.** В
|
||||
`calendar.html` и `my-conferences.html` правило `.avatar-btn span { color:
|
||||
var(--color-ink-900); }` (специфичность 0,1,1 — класс + тип-селектор) имело
|
||||
специфичность ВЫШЕ, чем `.avatar { color: #fff; }` (0,1,0 — один класс), и
|
||||
перебивало белый цвет у `<span class="avatar">` (он тоже `span` внутри
|
||||
`.avatar-btn`, значит попадал под общее правило `.avatar-btn span`) —
|
||||
инициалы рендерились тёмным `ink-900` на индиго-заливке, нечитаемо.
|
||||
Специфичность решает независимо от порядка правил в файле, поэтому простое
|
||||
переупорядочивание CSS не спасает. **Правило на будущее:** имя рядом с
|
||||
аватаром — всегда через адресный класс (`.user-name`, не голый `span`),
|
||||
никогда `.wrapper span` без уточнения — такой селектор непреднамеренно
|
||||
задевает `<span class="avatar">`, если он лежит в той же обёртке. Проверено
|
||||
headless-рендером (`getComputedStyle(...).color`) после фикса — `rgb(255,
|
||||
255, 255)` в обоих файлах; заодно проверены `admin.html`, `lobby.html`
|
||||
(структура другая — `.avatar-btn div span`/`.user-pill .avatar`, коллизии не
|
||||
было) — там уже было `rgb(255, 255, 255)` и до правки.
|
||||
|
||||
---
|
||||
|
||||
## 2. Типографика
|
||||
|
||||
| Роль | Шрифт | Fallback-стек | Где |
|
||||
|---|---|---|---|
|
||||
| Дисплей (display) | **Unbounded** | `'Arial Rounded MT Bold', -apple-system, 'Segoe UI', sans-serif` | H1 лендинг-подобных блоков, крупные цифры дашборда, логотип |
|
||||
| Текст (body/UI) | **Inter** | `-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif` | вся UI-типографика: формы, таблицы, кнопки, параграфы |
|
||||
| Моно (mono) | **JetBrains Mono** | `'SFMono-Regular', Menlo, Consolas, monospace` | код комнаты, ID брони, таймер конференции, .ics-детали |
|
||||
|
||||
Шрифты подключаются как **самостоятельно захостенные** (`frontend/public/fonts`,
|
||||
`@font-face` с относительным путём), CDN Google Fonts не используется нигде
|
||||
(в т.ч. в макетах) согласно правилу проекта «секреты и зависимости — не из
|
||||
внешних сетей на рантайме». В `tokens.css` объявлены `@font-face` с путями
|
||||
`../fonts/...`, отсутствие файлов в макетах безопасно деградирует в fallback-стек
|
||||
без сетевых запросов.
|
||||
|
||||
Unbounded выбран за геометричные, слегка скруглённые формы букв — это то же
|
||||
семейство характера, что у дисплейного заголовка в `referens.png` (широкие O/G/W,
|
||||
единая толщина штриха). Inter — для плотного кириллического текста интерфейса
|
||||
(отличная читаемость в мелких кеглях, стандарт де-факто для shadcn/ui).
|
||||
|
||||
### Шкала
|
||||
|
||||
| Токен | Размер / line-height | Начертание | Применение |
|
||||
|---|---|---|---|
|
||||
| `--text-display-2xl` | 64px / 1.05 | Unbounded 700 | Hero-заголовок (не используется в MVP-экранах, зарезервировано) |
|
||||
| `--text-display-xl` | 48px / 1.1 | Unbounded 700 | Крупные цифры (админ-дашборд, счётчики) |
|
||||
| `--text-display-lg` | 34px / 1.2 | Unbounded 600 | H1 экрана (заголовок лобби, «VidConf» в auth) |
|
||||
| `--text-h1` | 28px / 1.25 | Inter 700 | заголовок карточки/секции |
|
||||
| `--text-h2` | 22px / 1.3 | Inter 600 | подзаголовок |
|
||||
| `--text-h3` | 18px / 1.35 | Inter 600 | заголовок компонента (карточка комнаты, панель) |
|
||||
| `--text-body-lg` | 16px / 1.5 | Inter 400/500 | основной текст форм |
|
||||
| `--text-body` | 14px / 1.5 | Inter 400/500 | таблицы, списки, вторичный UI |
|
||||
| `--text-caption` | 12px / 1.4, uppercase, `letter-spacing: .04em` | Inter 600 | эйбрау-подписи `[ В КВАДРАТНЫХ СКОБКАХ ]`, статус-бейджи |
|
||||
| `--text-mono-sm` | 13px / 1.4 | JetBrains Mono 500 | код комнаты/брони |
|
||||
|
||||
---
|
||||
|
||||
## 3. Spacing, радиусы, тени, границы
|
||||
|
||||
### Spacing (8pt-грид с базой 4px)
|
||||
`--space-1: 4px · --space-2: 8px · --space-3: 12px · --space-4: 16px ·
|
||||
--space-5: 20px · --space-6: 24px · --space-8: 32px · --space-10: 40px ·
|
||||
--space-12: 48px · --space-16: 64px · --space-20: 80px · --space-24: 96px`
|
||||
|
||||
### Радиусы
|
||||
Крупные скругления карточек взяты из `referens.png` (визуально 24–32px).
|
||||
|
||||
`--radius-sm: 8px` (инпуты, чипы) · `--radius-md: 12px` (кнопки не-pill, теги) ·
|
||||
`--radius-lg: 16px` (плитки участников в комнате, пипетка collor_ico.png) ·
|
||||
`--radius-xl: 20px` (малые карточки) · `--radius-2xl: 24px` (карточки комнат/лобби) ·
|
||||
`--radius-3xl: 32px` (крупные панели, hero-блоки) · `--radius-full: 999px`
|
||||
(pill-кнопки, аватары, бейджи-статусы)
|
||||
|
||||
### Тени
|
||||
- `--shadow-sm` `0 1px 2px rgba(46,52,84,.06)` — лёгкий отрыв карточки от фона.
|
||||
- `--shadow-md` `0 4px 16px rgba(46,52,84,.08)` — карточка при hover.
|
||||
- `--shadow-lg` `0 12px 32px rgba(46,52,84,.12)` — модалки, поповеры.
|
||||
- `--shadow-glass` `0 8px 24px rgba(59,77,149,.15)` + `backdrop-filter: blur(16px)` — glass-панели (стат-виджеты).
|
||||
- `--shadow-speaker-glow` `0 0 0 3px var(--color-room-speaker-ring), 0 0 24px rgba(214,168,61,.35)` — свечение активного спикера (доп. к рамке).
|
||||
- `--shadow-room-panel` `0 8px 24px rgba(0,0,0,.4)` — панель чата/поповеры в тёмной теме.
|
||||
|
||||
### Границы
|
||||
`--border-thin: 1px` (базовая) · `--border-md: 1.5px` (pill-кнопки) ·
|
||||
`--border-thick: 2px` (hover/focus состояния) · `--border-speaker: 3px`
|
||||
(рамка активного спикера).
|
||||
|
||||
### Breakpoints (desktop-first)
|
||||
`--bp-desktop: 1440px` — расчётная ширина макета. `--bp-min: 1280px` — минимальная
|
||||
поддерживаемая ширина (контент перетекает, колонки не ломаются
|
||||
до 1280px; мобильная адаптация — вне скоупа).
|
||||
|
||||
### Motion
|
||||
Только микро-переходы состояний (hover/focus/toggle), без декоративных
|
||||
анимаций: `--transition-fast: 120ms ease`, `--transition-base: 200ms ease`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Компоненты и их состояния
|
||||
|
||||
### 4.1 Кнопка Primary (pill CTA, светлая тема)
|
||||
Форма: `--radius-full`, паддинг `12px 24px`, шрифт `--text-body-lg` / Inter 600.
|
||||
|
||||
| Состояние | Фон | Текст | Обводка |
|
||||
|---|---|---|---|
|
||||
| default | `--color-accent` | `--color-accent-text` | нет |
|
||||
| hover | `--color-accent-hover` | тот же | нет, `--shadow-md` |
|
||||
| active/pressed | `--color-accent-active` | тот же | нет |
|
||||
| focus-visible | как default | тот же | `2px --color-focus-ring`, offset 2px |
|
||||
| disabled | `--color-ink-300` фон 40% | `--color-surface` | нет, `cursor: not-allowed` |
|
||||
|
||||
### 4.2 Кнопка Secondary (pill outline, светлая тема)
|
||||
Прозрачный фон, обводка `1.5px --color-border-pill`, текст `--color-ink-700`.
|
||||
Hover: фон `--color-bg-alt`, обводка `--color-ink-700`. Focus-visible: кольцо
|
||||
`--color-focus-ring`. Disabled: обводка `--color-border`, текст `--color-ink-300`.
|
||||
|
||||
### 4.3 Кнопка Danger (светлая тема, напр. «Удалить комнату» в админке)
|
||||
Фон `--color-danger`, текст `#FFFFFF`, форма `--radius-full`. Hover — темнее на 8%.
|
||||
Всегда сопровождается иконкой (`Trash2`), не только цветом.
|
||||
|
||||
### 4.4 Инпут / форма
|
||||
Фон `--color-surface`, обводка `1px --color-border`, радиус `--radius-md`,
|
||||
паддинг `10px 14px`, текст `--text-body-lg`.
|
||||
|
||||
| Состояние | Обводка | Доп. |
|
||||
|---|---|---|
|
||||
| default | `--color-border` | — |
|
||||
| hover | `--color-border-strong` | — |
|
||||
| focus | `2px --color-focus-ring` | подпись-хинт индиго |
|
||||
| error | `2px --color-danger` | текст ошибки под полем, иконка `AlertCircle` слева |
|
||||
| disabled | `--color-border`, фон `--color-bg-alt` | текст `--color-ink-300` |
|
||||
|
||||
### 4.5 Карточка (светлая тема)
|
||||
Фон `--color-surface`, радиус `--radius-2xl`, обводка `1px --color-border`,
|
||||
тень `--shadow-sm` (по умолчанию) → `--shadow-md` (hover, если карточка кликабельна).
|
||||
Glass-вариант (стат-виджеты): `--color-surface-glass` + `backdrop-filter: blur(16px)`
|
||||
+ `--shadow-glass`, обводка `1px rgba(255,255,255,.6)`.
|
||||
|
||||
### 4.6 Бейдж статуса конференции (календарь, «Мои конференции»)
|
||||
Бейдж описывает жизненный цикл КОНФЕРЕНЦИИ (комнат в системе нет). Pill (`--radius-full`),
|
||||
паддинг `4px 12px` (мелкий вариант в сетке календаря — `2px 9px`, `font-size: 11px`),
|
||||
`--text-caption`, иконка 12–14px слева от текста.
|
||||
|
||||
| Статус | Фон | Текст | Иконка |
|
||||
|---|---|---|---|
|
||||
| Запланирована (разовая) | `--color-warning-bg` | `--color-warning` | `Clock` |
|
||||
| Закреплена · повторяется | `--color-bg-alt`, обводка `1px --color-border-strong` | `--color-ink-700` | `Repeat` |
|
||||
| Идёт сейчас | `--color-success-bg` | `--color-success` | `Play` |
|
||||
| Завершена | `--color-bg-alt` | `--color-ink-500` | `CheckCircle2` |
|
||||
| Отменена | `--color-danger-bg` | `--color-danger` | `X`, заголовок карточки — `line-through` |
|
||||
|
||||
Закрытость паролем — НЕ отдельный статус жизненного цикла (ортогональна ему:
|
||||
запланированная, закреплённая или идущая сейчас конференция может быть
|
||||
закрытой одновременно). Отображается отдельно — маленькая иконка `Lock`
|
||||
(13–15px, `--color-ink-700`/`--color-room-text-secondary`) инлайном перед
|
||||
названием конференции, не бейджем.
|
||||
|
||||
### 4.7 Плитка участника (комната, тёмная тема)
|
||||
Фон `--color-room-tile`, радиус `--radius-lg`, обводка `1px --color-room-tile-border`.
|
||||
Имя — верхний левый угол, `--text-body` / `--color-room-text-primary`, поверх
|
||||
полупрозрачной подложки-градиента снизу-вверх для читаемости на видео.
|
||||
Иконка микрофона — нижний левый угол, 20px, круглая подложка `rgba(0,0,0,.35)`.
|
||||
|
||||
| Состояние | Обводка/эффект | Микрофон |
|
||||
|---|---|---|
|
||||
| default (не говорит) | `--color-room-tile-border` | `Mic` зелёный, если не muted |
|
||||
| активный спикер | `--border-speaker` `--color-room-speaker-ring` + `--shadow-speaker-glow` | форма рамки — единственный, кто её имеет |
|
||||
| muted | обводка как default | `MicOff` `--color-room-mic-off`, иконка перечёркнута (не только цвет) |
|
||||
| видео выключено | вместо видео — аватар-заглушка на `--color-room-tile-hover` | значок `VideoOff` в углу |
|
||||
| pinned/развёрнута | увеличенный размер в сетке, иконка `Pin` активна | — |
|
||||
| hover (крупная плитка) | показывается toolbar-оверлей (`Maximize2`, `MoreVertical`) | — |
|
||||
|
||||
### 4.8 Нижний тулбар комнаты (тёмная тема)
|
||||
Фон `--color-room-surface`, высота 72px, иконки контурные 24px, подпись под
|
||||
иконкой `--text-caption` `--color-room-text-secondary`.
|
||||
|
||||
| Кнопка | Цвет по умолчанию | Toggled-off/mute | Позитивное действие |
|
||||
|---|---|---|---|
|
||||
| Микрофон | `--color-room-text-primary` outline | фон `--color-room-mic-off` заливка, иконка `MicOff` | — |
|
||||
| Камера | `--color-room-text-primary` outline | фон `--color-room-mic-off`, иконка `VideoOff` | — |
|
||||
| Демонстрация экрана | `--color-room-text-primary` outline | — | active-состояние: иконка/подпись `--color-room-mic-on` |
|
||||
| Чат | `--color-room-text-primary` outline | — | активна (панель открыта): подложка `--color-room-surface-raised` |
|
||||
| Участники | `--color-room-text-primary` outline | — | бейдж-счётчик `--color-room-mic-on` |
|
||||
| Выйти | обводка `--color-room-danger` | — | всегда красная, hover → заливка `--color-room-danger-bg` |
|
||||
|
||||
### 4.9 Панель чата (комната)
|
||||
Свёрнута: не занимает места, доступна кнопкой в тулбаре (см. 4.8).
|
||||
Развёрнута: правая колонка 320px, фон `--color-room-surface-raised`,
|
||||
разделитель `1px --color-room-tile-border`, сетка участников сужается.
|
||||
Сообщения — пузыри `--color-room-tile`, имя автора `--color-room-mic-on`
|
||||
(лёгкий акцент, не критично для контраста т.к. декоративно), текст
|
||||
`--color-room-text-primary`.
|
||||
|
||||
### 4.10 Ячейка календаря-менеджера
|
||||
Календарь не бронирует переговорные (нет привязки к комнате, нет конфликтов
|
||||
брони по `EXCLUDE`-констрейнту БД) — это менеджер запланированных/закреплённых
|
||||
КОНФЕРЕНЦИЙ. Единица сетки —
|
||||
дневная колонка (`--radius-lg`, фон `--color-bg-alt`, `min-height: 460px`),
|
||||
внутри — стопка карточек-конференций («чипов», `--radius-md`, `--text-body`).
|
||||
|
||||
| Состояние | Фон/обводка | Текст/иконка |
|
||||
|---|---|---|
|
||||
| День без конференций | колонка `--color-bg-alt` | подпись-заглушка `--color-ink-300` по центру («Свободный день» / «Ещё нет конференций») |
|
||||
| Сегодняшняя колонка | лёгкая подсветка `rgba(212,242,227,.16)` (тон `--color-accent`) | заголовок дня `--color-ink-700` |
|
||||
| Чип конференции (default) | `--color-surface`, обводка `1px --color-border` | бейдж статуса §4.6 сверху, hover → `--shadow-sm` + `--color-border-strong` |
|
||||
| Чип отменённой конференции | как default, `opacity: .6` | заголовок `line-through`, `--color-ink-500` |
|
||||
| Закрытая паролем | как default | иконка `Lock` инлайн перед названием (см. §4.6) |
|
||||
|
||||
### 4.11 Таблица (админка)
|
||||
Заголовок `--color-bg-alt`, `--text-caption` uppercase `--color-ink-500`.
|
||||
Строки — разделитель `1px --color-border`, hover-строка `--color-bg-alt`.
|
||||
Действия — icon-button (`Pencil`, `Trash2`, `Mail`) 32px, `--radius-md`.
|
||||
|
||||
### 4.12 Карточка-действие хаба (лобби)
|
||||
Лобби — хаб из 4 плиток-действий вокруг медальона VidConf
|
||||
(`design/mockups/lobby.html`), а не список комнат. Фон `--color-surface`,
|
||||
радиус `--radius-3xl`, обводка `1px --color-border`, тень `--shadow-sm`.
|
||||
**Фиксированная высота** (не `min-height`) у всех 4 плиток — обязательное
|
||||
условие строгой симметрии сетки 2×2 независимо от длины текста внутри.
|
||||
|
||||
Тайл `816×340px` (при доступной ширине ≥ ~1728px; на меньших вьюпортах,
|
||||
вплоть до `--bp-min: 1280px`, сетка пропорционально сжимается через
|
||||
`width: min(1664px, 100%)` на контейнере `.hub`, оставаясь строго
|
||||
симметричной 2×2, без переполнения); контейнер `.hub` — `1664px`, gap между
|
||||
тайлами — `--space-8` (32px). Типографика внутри: заголовок `40px/1.2`
|
||||
(`54px/1.1` для акцентной плитки «Создать»), подпись-объект `21px`,
|
||||
примечание `17px`.
|
||||
|
||||
Медальон-иконка (68px, круг, обводка `--border-thick --color-ink-700`)
|
||||
вынесен на верхнюю кромку карточки (`position: absolute; top: -34px`),
|
||||
под него на самой карточке зарезервирован `margin-top: 34px`. Заголовок
|
||||
`--color-ink-700`, uppercase; подзаголовок-объект `--color-ink-900`;
|
||||
примечание `--color-ink-500`.
|
||||
|
||||
Главное действие («Создать») — акцентный вариант на пастельной мяте
|
||||
(`--color-accent`), текст и медальон — `--color-accent-text`.
|
||||
|
||||
| Состояние | Фон (обычная / акцентная) | Эффект |
|
||||
|---|---|---|
|
||||
| default | `--color-surface` / `--color-accent` | `--shadow-sm` |
|
||||
| hover | тот же / `--color-accent-hover` | `--shadow-lg`, `translateY(-4px)` |
|
||||
| active | тот же / `--color-accent-active` | `translateY(-1px)` |
|
||||
| focus-visible | тот же | кольцо `--color-focus-ring`, offset 2px |
|
||||
|
||||
**Медальон VidConf в центре** — декоративный (`pointer-events: none`,
|
||||
`aria-hidden`), диаметр `156px`, фон `--color-surface`
|
||||
(непрозрачный), обводка `--border-thick --color-ink-700`, тень
|
||||
`--shadow-lg`, `z-index: 20` (гарантированно поверх карточек). Содержимое —
|
||||
знак объектива `logo.svg` (`56×56px`, §1.4) + слово «VidConf» под ним
|
||||
(`600 16px var(--font-display)`, `--color-ink-700`).
|
||||
|
||||
Медальон не просто сидит в промежутке сетки, а **лежит поверх** равных по
|
||||
величине частей видимых углов всех 4 карточек. Геометрия фиксированная (в пикселях,
|
||||
не в процентах — высота `.hub` всегда `2×340 + 32 = 712px`, независимо от
|
||||
ширины вьюпорта):
|
||||
- по горизонтали асимметрии нет — центр строго на границе колонок, `16px`
|
||||
(половина `gap`) до ближнего края каждой карточки;
|
||||
- по вертикали — видимый низ карточек ряда 1 на `y = 340px` от верха `.hub`,
|
||||
видимый верх карточек ряда 2 на `y = 406px` (`gap` 32px + `margin-top`
|
||||
34px карточки под собственный медальон-иконку); равноудалённая от обеих
|
||||
точка — `y = 373px` (`33px` до каждой), а **не** геометрический центр
|
||||
сетки `356px` (`712 / 2`), — из-за несимметричного резерва под
|
||||
action-icon только у нижнего ряда.
|
||||
|
||||
Медальон центрирован в `(50%, 373px)` (`top: 373px; left: 50%;
|
||||
transform: translate(-50%, -50%)`), а не `(50%, 50%)`. Такая точка —
|
||||
зеркально-симметричная по обеим осям относительно всех 4 карточек (что
|
||||
подтверждается тем, что сдвиг `16px`/`33px` — общий для обеих карточек по
|
||||
каждой оси, а не индивидуальный для одной), поэтому перекрытие получается
|
||||
одинаковым на все 4 угла **по построению**, без ручной подгонки под каждый
|
||||
угол. Проверено headless-рендером (Playwright/Chromium, 1280/1440/1728px):
|
||||
перекрытие видимой (с учётом скругления `--radius-3xl`, 32px) части каждой
|
||||
из 4 карточек — **29.2px**, идентично на всех четырёх и на всех проверенных
|
||||
ширинах вьюпорта (симметрия по x/y — расхождение 0px).
|
||||
|
||||
### 4.13 Карточка создания конференции (календарь)
|
||||
Фон `--color-surface`, радиус `--radius-2xl`, паддинг `--space-6`, sticky
|
||||
в двухколоночном layout календаря. Поля «Название / Дата / Время /
|
||||
Участники» — стандартный инпут §4.4. Два тумблера общего компонента
|
||||
`.switch`/`.slider` (заливка `--color-accent-active` во включённом состоянии):
|
||||
|
||||
- **«Закрытая по паролю»** — раскрывает поле пароля под тумблером.
|
||||
- **«Закрепить постоянную конференцию»** — раскрывает блок повторений
|
||||
(§4.14) со всеми 4 вариантами сразу.
|
||||
|
||||
Оба раскрытия — чистый CSS через `:has()` (без JS): тумблер и раскрываемый
|
||||
блок — сиблинги внутри общего `.pin-block`/`.toggle-block`,
|
||||
`:has(input:checked) .sub-block { display: … }`. При адаптации на
|
||||
`frontend/` — то же поведение через `useState`/controlled-компонент.
|
||||
|
||||
### 4.14 Блок повторений (recurrence)
|
||||
Раскрывается под тумблером «Закрепить постоянную конференцию» (§4.13),
|
||||
отделён `1px dashed --color-border`. Список из 4 вариантов
|
||||
(`.recurrence-option`, radio-группа — выбран ровно один):
|
||||
|
||||
| Вариант | Доп. поля | Обязательная подсказка (edge cases) |
|
||||
|---|---|---|
|
||||
| По дням недели, еженедельно | 7 круглых чипов дней недели (34px, множественный выбор) | ближайшие 2 даты вхождения |
|
||||
| Раз в 2 недели | дата начала + день недели | ближайшие 2 даты вхождения |
|
||||
| Раз в месяц | число месяца (1–31) | «если в месяце нет такого числа — переносится на последний день месяца» (31-е число, 29 февраля) |
|
||||
| Каждые N дней | целое N + дата начала | ближайшие даты вхождения («через N, 2N, 3N дней») |
|
||||
|
||||
Строка варианта: `--radius-md`, обводка `1px --color-border` → при выборе
|
||||
обводка `--color-ink-700` + фон `--color-bg-alt` (через `:has(input:checked)`,
|
||||
см. §4.13). Под-поля выбранного варианта раскрываются тем же CSS-приёмом;
|
||||
у невыбранных — скрыты (`display: none`), а не задизейблены — чтобы список
|
||||
из 4 строк был виден целиком сразу после раскрытия тумблера. Выбранный день недели/чип — заливка `--color-ink-700`, текст
|
||||
белый. Подсказки — `12px`, `--color-ink-500`.
|
||||
|
||||
### 4.15 Пилюли «ссылка / номер» с копированием
|
||||
Новый компонент «Моих конференций»: у каждой конференции есть постоянная
|
||||
ссылка и человеко-диктуемый номер, доступные для копирования одним кликом.
|
||||
Подпись-заголовок над пилюлей — `--text-caption`, `--color-ink-400`
|
||||
(«Ссылка» / «Номер»). Пилюля — `--radius-full`, фон `--color-bg-alt`,
|
||||
обводка `1px --color-border`; значение — `--text-mono-sm` `--color-ink-700`,
|
||||
обрезается `text-overflow: ellipsis` (для длинных ссылок). Кнопка
|
||||
копирования — круг 28px справа внутри пилюли.
|
||||
|
||||
| Состояние кнопки копирования | Иконка | Доп. |
|
||||
|---|---|---|
|
||||
| default | `Copy`, `--color-ink-500` | — |
|
||||
| hover | `Copy`, `--color-ink-700` | `--shadow-sm` |
|
||||
| скопировано (~1.5s) | `Check`, `--color-success` | всплывающая подсказка «Скопировано» над кнопкой |
|
||||
| focus-visible | как default | кольцо `--color-focus-ring` |
|
||||
|
||||
Статус «скопировано» не передаётся только сменой цвета иконки — обязательно
|
||||
дублируется сменой формы иконки (`Copy` → `Check`) и текстовым тултипом.
|
||||
|
||||
### 4.16 Селект (кастомный выпадающий список)
|
||||
|
||||
Заменяет нативный `<select>` в полях
|
||||
«Рассылка саммари» (карточка создания/редактирования конференции, §4.13) и
|
||||
«Получатели по умолчанию» (настройки админки), а также любой будущий
|
||||
одиночный выбор из списка. Собран из уже утверждённых примитивов — триггер
|
||||
переиспользует геометрию инпута (§4.4), дроплист переиспользует паттерн
|
||||
`.menu` (выпадающее меню бургера лобби, `design/mockups/lobby.html`), пункт
|
||||
списка — паттерн `.menu a`. Ничего нового в токенах не заводится.
|
||||
|
||||
**Триггер (закрытое состояние).** Высота `44px` (паддинг `10px 14px` +
|
||||
`--text-body-lg` при `line-height: 1.5` — идентично инпуту §4.4, чтобы
|
||||
селект и текстовые поля в одной форме были на одной высоте), фон
|
||||
`--color-surface`, обводка `1px --color-border`, радиус `--radius-md`.
|
||||
Значение слева, шеврон `ChevronDown` (18px, `--color-ink-500`) прижат
|
||||
вправо. Текст — `--text-body-lg` `--color-ink-900`; если значение не
|
||||
выбрано — плейсхолдер `--color-ink-300` (та же роль токена, что у
|
||||
плейсхолдера инпута).
|
||||
|
||||
| Состояние | Обводка/фон | Текст/шеврон | Доп. |
|
||||
|---|---|---|---|
|
||||
| default | `--color-border` | `--color-ink-900` / шеврон `--color-ink-500` | — |
|
||||
| hover | `--color-border-strong` | те же | курсор `pointer` |
|
||||
| focus-visible (закрыт) | `2px --color-focus-ring` | те же | как фокус инпута §4.4 |
|
||||
| открыт | `2px --color-focus-ring`, нижние углы дроплиста продолжают радиус триггера визуально (панель — отдельный блок, см. ниже) | шеврон повёрнут `rotate(180deg)`, `--transition-fast` | `aria-expanded="true"` |
|
||||
| error | `2px --color-danger` | текст `--color-ink-900` | текст ошибки под полем + `AlertCircle` слева от значения, как у инпута §4.4 |
|
||||
| disabled | `--color-border`, фон `--color-bg-alt` | текст и шеврон `--color-ink-300` | курсор `not-allowed`, `aria-disabled="true"` |
|
||||
|
||||
**Дроплист (открытое состояние).** `position: absolute`, `top: calc(100% + 6px)`,
|
||||
ширина равна триггеру (`width: 100%`, в отличие от `.menu`, у которого
|
||||
`min-width` фиксирован — селект обязан совпадать по ширине с полем, иначе
|
||||
теряется визуальная связь триггер↔панель). Фон `--color-surface`, обводка
|
||||
`1px --color-border`, радиус `--radius-lg`, тень `--shadow-lg`, внутренний
|
||||
паддинг `--space-2` (8px), `z-index: 30` (над соседними карточками формы).
|
||||
Список длиннее ~7 пунктов — `max-height: 280px; overflow-y: auto` со
|
||||
стандартным скроллбаром (не кастомизируется, см. `color-scheme` в
|
||||
`tokens.css`, влияет на нативный скроллбар браузера в обеих темах).
|
||||
|
||||
Пункт списка (`role="option"`): паддинг `10px 14px`, радиус `--radius-sm`,
|
||||
`--text-body-lg` 400, текст `--color-ink-900`, `display: flex; justify-content: space-between`
|
||||
(значение слева, место под иконку `Check` справа).
|
||||
|
||||
| Состояние пункта | Фон | Текст | Доп. индикатор |
|
||||
|---|---|---|---|
|
||||
| default (не выбран, не наведён) | прозрачный | `--color-ink-900` | — |
|
||||
| hover / клавиатурная подсветка (`aria-activedescendant`) | `--color-bg-alt` | `--color-ink-700` | — |
|
||||
| выбран (`aria-selected="true"`) | прозрачный (или `--color-bg-alt`, если одновременно наведён/подсвечен — состояния комбинируются аддитивно) | `--color-ink-900`, `font-weight: 600` | иконка `Check` (16px, `--color-ink-700`) справа — «выбрано» никогда не передаётся только фоном/цветом |
|
||||
| disabled-пункт | прозрачный | `--color-ink-300` | `cursor: not-allowed`, `aria-disabled="true"`, без hover-эффекта |
|
||||
|
||||
Опциональный разделитель групп внутри списка — тот же приём, что
|
||||
`.menu-divider`: `1px --color-border`, `margin: var(--space-2) 6px`.
|
||||
|
||||
**Клавиатурная доступность и ARIA.** Паттерн — listbox-combobox
|
||||
(`role="combobox"` на триггере + `role="listbox"` на панели), без нативного
|
||||
`<select>`, чтобы стилизация дроплиста была полностью в токенах проекта.
|
||||
|
||||
| Действие | Клавиша | Поведение |
|
||||
|---|---|---|
|
||||
| Открыть список | `Enter`, `Space`, `ArrowDown`, `ArrowUp` (фокус на триггере, список закрыт) | раскрывает дроплист; подсветка ставится на выбранный пункт (если значения ещё нет — на первый) |
|
||||
| Навигация | `ArrowDown` / `ArrowUp` (список открыт) | двигает клавиатурную подсветку на следующий/предыдущий пункт (циклически не переходит с последнего на первый), обновляет `aria-activedescendant` триггера — фокус физически остаётся на триггере |
|
||||
| К началу/концу | `Home` / `End` | подсветка на первый/последний доступный (не disabled) пункт |
|
||||
| Быстрый переход | ввод буквы | подсветка прыгает на ближайший следующий пункт, начинающийся с введённого символа (стандартное ожидание пользователей от `<select>`) |
|
||||
| Выбрать и закрыть | `Enter`, `Space` (на подсвеченном пункте) | фиксирует значение, закрывает панель, фокус остаётся на триггере, `aria-expanded="false"` |
|
||||
| Закрыть без изменений | `Esc` | закрывает панель, значение не меняется, фокус остаётся на триггере |
|
||||
| Закрыть без изменений | клик вне триггера/панели, `Tab` | панель закрывается, значение не меняется (если пункт не был явно выбран через `Enter`/клик) |
|
||||
|
||||
ARIA-контракт: триггер — `role="combobox"`, `aria-haspopup="listbox"`,
|
||||
`aria-expanded`, `aria-controls="<id панели>"`, `aria-labelledby`/`aria-label`
|
||||
(подпись поля, как у инпута §4.4); панель — `role="listbox"`,
|
||||
`aria-activedescendant` на триггере указывает на `id` текущего подсвеченного
|
||||
`role="option"`; каждый пункт — `aria-selected`, disabled-пункты —
|
||||
`aria-disabled="true"` и исключены из навигации стрелками/type-ahead.
|
||||
|
||||
**Обе темы.** Компонент целиком собран из уже описанных выше токенов
|
||||
(`--color-surface`, `--color-border(-strong)`, `--color-ink-*`,
|
||||
`--color-focus-ring`, `--color-danger`, `--shadow-lg`, `--radius-md/lg/sm`),
|
||||
которые уже переключаются между светлой и тёмной оболочкой по механизму
|
||||
§0.1 — отдельной тёмной версии спеки не требуется. Прецедент — `.menu`
|
||||
(дроплист бургер-меню лобби): его CSS в `design/mockups/lobby.html` и
|
||||
`design/mockups/dark/lobby.html` побайтово идентичен, меняется только
|
||||
подстановка переменных из `tokens.css`; Select использует ту же панель
|
||||
целиком на var(), поэтому корректно рендерится в тёмной теме без правок.
|
||||
В тёмной оболочке фон панели/триггера — графитовый `--color-surface`
|
||||
(`#2A2A2A`), подсветка пункта — `--color-bg-alt` (`#242424`), выбранный
|
||||
пункт и hover-текст — пастельная мята `--color-ink-700` (`#7FDDA8`), кольцо
|
||||
фокуса — янтарь `--color-focus-ring` (`#D6A83D`), см. §1.2b.
|
||||
|
||||
---
|
||||
|
||||
## 5. Доступность
|
||||
|
||||
- Текст ≥ 4.5:1 везде, кроме крупных декоративных элементов (отмечены ⚠︎ выше,
|
||||
≥ 3:1 по правилу WCAG для large text).
|
||||
- Статус всегда дублируется не цветом: иконка (статус конференции §4.6,
|
||||
mic on/off, «скопировано» при копировании ссылки/номера) + текстовая подпись.
|
||||
- `:focus-visible` — везде видимое кольцо (`--color-focus-ring` светлая тема,
|
||||
`--color-room-focus-ring` тёмная), никогда `outline: none` без замены.
|
||||
- Интерактивные элементы ≥ 40×40px области клика (кнопки тулбара — 48×48px).
|
||||
- Все декоративные SVG-иконки — `aria-hidden="true"`, интерактивные — с
|
||||
`aria-label` на кнопке-обёртке.
|
||||
- Логотип (§1.4) в топбаре — `role="img" aria-label="VidConf"` на SVG внутри
|
||||
`.brand-mark`, где рядом уже есть текстовое «VidConf» — SVG физически
|
||||
декоративен (`aria-hidden="true"` в разметке), словесный знак читает
|
||||
скринридер. Медальон лобби — весь блок `aria-hidden="true"` (дублирует
|
||||
бренд, не несёт уникальной информации).
|
||||
- Аватары с инициалами (§1.5) — текст инициалов всегда светлый (`#FFFFFF`)
|
||||
на заливке `--color-ink-700`/`--color-ink-500`, ≥ 4.5:1 (см.
|
||||
`design/tools/contrast.py`).
|
||||
|
||||
---
|
||||
|
||||
## 6. Имплементация для frontend-dev
|
||||
|
||||
### shadcn/ui
|
||||
Тема подключается через CSS-переменные `tokens.css`, а не через хардкод в
|
||||
`tailwind.config`. Компоненты shadcn/ui кастомизировать так:
|
||||
|
||||
| shadcn/ui компонент | Что переопределить | Токены |
|
||||
|---|---|---|
|
||||
| `Button` | варианты `default` (pill CTA), `outline` (pill secondary), `destructive`, `ghost` (иконки тулбара) | `--color-accent*`, `--color-border-pill`, `--color-danger*`, `--radius-full` |
|
||||
| `Input`, `Textarea` | border/focus/error-состояния | `--color-border`, `--color-focus-ring`, `--color-danger` |
|
||||
| `Card` | радиус, тень, glass-вариант отдельным классом `.card-glass` | `--radius-2xl`, `--shadow-sm/md`, `--shadow-glass` |
|
||||
| `Badge` | варианты `success` / `danger` / `warning` / `neutral` под статусы конференций (§4.6) | цвета из 1.1 |
|
||||
| `Dialog` / `Sheet` | модалка брони, панель чата (Sheet справа в тёмной теме) | `--shadow-lg`, `--color-room-surface-raised` |
|
||||
| `Select` (кастомный, §4.16, не нативный `<select>`) | триггер как `Input` (§4.4), дроплист — паттерн `.menu` (лобби) | триггер `--radius-md`; панель `--radius-lg` + `--shadow-lg`; пункт `--radius-sm`; `--color-border`, `--color-focus-ring` |
|
||||
| `Tabs` | Вход/Регистрация на auth | `--color-ink-700` активная вкладка, подчёркивание `--color-accent-active` |
|
||||
| `Table` | админка | `--color-bg-alt` заголовок, `--color-border` разделители |
|
||||
| `Toggle`/`ToggleGroup` | переключатель layout сетки в комнате, кнопки mic/camera | активное состояние `--color-room-mic-on` / `--color-room-mic-off` |
|
||||
| `Calendar` (FullCalendar, не shadcn) | обёртка токенами, ячейки — см. 4.10 | — |
|
||||
| `Avatar` | участники комнаты, аватары в лобби/админке | `--radius-full`; заливка/текст инициалов — см. §1.5 (заливка `ink-700`/`ink-500`, текст всегда `#FFFFFF`) |
|
||||
| `Tooltip` | подсказки тулбара | `--color-room-surface-raised` |
|
||||
|
||||
Тёмная тема room включается локальным скоупом (напр. `data-theme="room"` на
|
||||
контейнере страницы конференции), а не глобальным `dark:` — остальная оболочка
|
||||
всегда светлая независимо от системной темы ОС.
|
||||
|
||||
### Иконки — lucide-react, контурные, `stroke-width: 2`
|
||||
|
||||
| Экран | Иконки |
|
||||
|---|---|
|
||||
| **auth** | `Mail`, `Lock`, `Eye`/`EyeOff`, `User`, `CheckCircle2` (подтверждён email), `ArrowRight` |
|
||||
| **lobby** (хаб действий) | `Video` («Создать»), `CalendarDays` («Календарь»), `Link2` («Подключиться»), `User` («Мои»), бургер-меню: `Download`, `Info`, `LayoutDashboard`, `LogOut` |
|
||||
| **room** | `Mic`, `MicOff`, `Video`, `VideoOff`, `ScreenShare`, `MessageSquare`, `PhoneOff` (выход), `Users`, `Settings`, `LayoutGrid`, `Columns`, `Grid2x2`, `Maximize2`, `MoreVertical`, `Pin`, `Send`, `ChevronLeft`/`ChevronRight` (лента превью) |
|
||||
| **calendar** (менеджер конференций) | `ChevronLeft`/`ChevronRight`, `CalendarDays`, `Clock`, `Lock`, `AlertTriangle`, `Check`, `Plus`, `Repeat` (закреплена), `Play` (идёт сейчас), `X` (отменена/отмена), `ChevronDown` (селект «Рассылка саммари», §4.16) |
|
||||
| **my-conferences** | `Repeat`, `Clock`, `CheckCircle2`, `Users`, `Copy`/`Check` (копирование), `Play` («Войти»), `Pencil`, `FileText` (саммари), `Lock` |
|
||||
| **join** | `Link2`, `Hash`, `User`, `Mail`, `Lock`, `Eye`/`EyeOff`, `ArrowRight`, `AlertCircle`, `Video` (карточка целевой конференции) |
|
||||
| **admin** | `LayoutDashboard`, `Video`, `Lock`, `Users`, `Mail`, `Trash2`, `Pencil`, `Search`, `Plus`, `Send`, `ChevronDown`/`Check` (селект «Получатели по умолчанию», §4.16) |
|
||||
| **email-summary** | инлайн-SVG (email-клиенты не грузят icon-шрифты/JS): `CheckCircle2`, `ListChecks`, `HelpCircle`, `BarChart3`, стрелка CTA |
|
||||
|
||||
В HTML-макетах иконки встроены как инлайн-SVG (без иконных шрифтов и CDN);
|
||||
во frontend — заменяются на `lucide-react` компоненты 1:1 по смыслу.
|
||||
|
||||
### Файлы
|
||||
`design/tokens.css` — источник истины по значениям (используется напрямую или
|
||||
транслируется в `tailwind.config.*`, см. `design/tailwind.md`).
|
||||
|
||||
### Макеты (`design/mockups/`, единый источник истины по разметке)
|
||||
- `auth.html` — форма входа/регистрации
|
||||
- `lobby.html` — хаб действий (2×2 плитки)
|
||||
- `calendar.html` — менеджер запланированных конференций
|
||||
- `my-conferences.html` — мои конференции (закреплённые и предстоящие)
|
||||
- `join.html` — подключение по ссылке/номеру → представиться → пароль
|
||||
- `room.html` — конференция (тёмная тема)
|
||||
- `admin.html` — администрирование
|
||||
- `email-summary.html` — экспорт-письмо с транскриптом и резюме (всегда
|
||||
светлое — почтовые клиенты в массе не поддерживают тёмную тему/CSS-переменные,
|
||||
не участвует в переключателе тем оболочки)
|
||||
- `dark/*.html` — тёмный вариант оболочки (6 экранов: `login`, `lobby`,
|
||||
`calendar`, `join`, `my-conferences`, `admin`); палитра — §1.2b, см. `dark/README.md` за происхождением цветов
|
||||
14
design/favicon.svg
Normal file
14
design/favicon.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
VidConf — упрощённая версия логотипа «объектив» для мелких размеров
|
||||
(16–32px: favicon, app-иконка, значок в топбаре рядом со словом «VidConf»).
|
||||
Меньше деталей и толще штрихи, чем в мастер-файле design/logo.svg, — чтобы
|
||||
силуэт читался на маленьком экране. Один цвет — currentColor (наследует
|
||||
var(--color-ink-700), #3B4D95). Квадратный viewBox 32×32.
|
||||
-->
|
||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" fill="none" role="img" aria-label="VidConf">
|
||||
<!-- диафрагма (закрытая, плоский шестигранник) -->
|
||||
<polygon points="21,16 18.5,20.3 13.5,20.3 11,16 13.5,11.7 18.5,11.7" fill="currentColor"/>
|
||||
<!-- одно широкое разорванное кольцо — читается как объектив даже в 16px -->
|
||||
<circle cx="16" cy="16" r="13" stroke="currentColor" stroke-width="2.6" stroke-dasharray="12 6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
28
design/logo.svg
Normal file
28
design/logo.svg
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
VidConf — мастер-логотип «объектив».
|
||||
Конструкция: центральная закрытая диафрагма (плоская шестигранная линза) +
|
||||
4 концентрических кольца, каждое разорвано на дуги-сегменты разной длины
|
||||
и толщины с неравномерными промежутками (техно/HUD-эстетика, ощущение
|
||||
вращения диафрагменных лепестков). Один цвет — currentColor (в проекте
|
||||
наследует var(--color-ink-700), #3B4D95, на светлых поверхностях).
|
||||
Квадратный viewBox 64×64, масштабируется без потери пропорций.
|
||||
Для размеров 16–32px использовать design/favicon.svg (упрощённая версия).
|
||||
Подробности и правила использования — design/DESIGN_SYSTEM.md, раздел «Логотип».
|
||||
-->
|
||||
<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" fill="none" role="img" aria-label="VidConf">
|
||||
<!-- диафрагма (закрытая, плоский шестигранник) -->
|
||||
<polygon points="41,32 36.5,39.8 27.5,39.8 23,32 27.5,24.2 36.5,24.2" fill="currentColor"/>
|
||||
<!-- кольцо 1 -->
|
||||
<circle cx="32" cy="32" r="15" stroke="currentColor" stroke-width="3"
|
||||
stroke-dasharray="16 7 28 9 12 16" stroke-dashoffset="5"/>
|
||||
<!-- кольцо 2 -->
|
||||
<circle cx="32" cy="32" r="20" stroke="currentColor" stroke-width="2"
|
||||
stroke-dasharray="22 10 8 14 34 12 6 10" stroke-dashoffset="40"/>
|
||||
<!-- кольцо 3 -->
|
||||
<circle cx="32" cy="32" r="25" stroke="currentColor" stroke-width="3.5"
|
||||
stroke-dasharray="40 14 12 10 26 16 20 8" stroke-dashoffset="80"/>
|
||||
<!-- кольцо 4 (внешнее, самое тонкое) -->
|
||||
<circle cx="32" cy="32" r="29" stroke="currentColor" stroke-width="1.6"
|
||||
stroke-dasharray="10 6 60 20 30 10 24 8" stroke-dashoffset="120"/>
|
||||
</svg>
|
||||
232
design/mockups/admin.html
Normal file
232
design/mockups/admin.html
Normal file
@@ -0,0 +1,232 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>VidConf — Админка</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../tokens.css">
|
||||
<style>
|
||||
html { min-width: 1280px; }
|
||||
|
||||
.app-shell { display: flex; min-height: 100vh; }
|
||||
|
||||
/* ---------- Сайдбар ---------- */
|
||||
.sidebar { width: 260px; flex-shrink: 0; background: var(--color-surface); border-right: 1px solid var(--color-border); padding: var(--space-6) var(--space-4); display: flex; flex-direction: column; }
|
||||
.brand-mark { display: inline-flex; align-items: center; gap: 8px; font: var(--text-h3); color: var(--color-ink-700); padding: 0 var(--space-2); margin-bottom: var(--space-8); }
|
||||
.brand-mark .logo-mark { width: 22px; height: 22px; flex-shrink: 0; }
|
||||
|
||||
.side-nav { display: flex; flex-direction: column; gap: 2px; }
|
||||
.side-link {
|
||||
display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-radius: var(--radius-md);
|
||||
font: var(--text-body); font-weight: 600; color: var(--color-ink-500); text-decoration: none;
|
||||
}
|
||||
.side-link:hover { background: var(--color-bg-alt); color: var(--color-ink-900); }
|
||||
.side-link.is-active { background: var(--color-bg-alt); color: var(--color-ink-700); }
|
||||
.side-link .icon { color: inherit; }
|
||||
|
||||
.side-footer { margin-top: auto; padding: var(--space-3) var(--space-2); border-top: 1px solid var(--color-border); }
|
||||
.avatar-btn { display: flex; align-items: center; gap: 8px; padding: 8px 0; width: 100%; background: none; border: none; text-align: left; }
|
||||
.avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--color-ink-700); color: #fff; display:flex; align-items:center; justify-content:center; font: var(--text-caption); text-transform: none; letter-spacing: 0; flex-shrink: 0; }
|
||||
.avatar-btn div strong { display: block; font: var(--text-body); color: var(--color-ink-900); }
|
||||
.avatar-btn div span { font: 12px var(--font-body); color: var(--color-ink-500); }
|
||||
|
||||
/* ---------- Контент ---------- */
|
||||
main { flex: 1; padding: var(--space-8); max-width: 1220px; }
|
||||
.page-head { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: var(--space-6); }
|
||||
.page-head h1 { font: var(--text-display-lg); font-family: var(--font-display); color: var(--color-ink-700); margin: 0 0 6px; }
|
||||
.page-head p { font: var(--text-body-lg); color: var(--color-ink-500); margin: 0; }
|
||||
|
||||
.btn { display: inline-flex; align-items: center; gap: 8px; border: none; cursor: pointer; font-family: var(--font-body); white-space: nowrap; transition: background var(--transition-fast), box-shadow var(--transition-fast); }
|
||||
.btn-primary { background: var(--color-accent); color: var(--color-accent-text); font: var(--text-body); font-weight: 700; padding: 11px 22px; border-radius: var(--radius-full); }
|
||||
.btn-primary:hover { background: var(--color-accent-hover); box-shadow: var(--shadow-md); }
|
||||
.btn-secondary { background: transparent; color: var(--color-ink-700); border: 1.5px solid var(--color-border-pill); font: var(--text-body); font-weight: 600; padding: 9px 18px; border-radius: var(--radius-full); }
|
||||
.btn-secondary:hover { background: var(--color-bg-alt); }
|
||||
.btn-danger { background: var(--color-danger); color: #fff; font: var(--text-body); font-weight: 700; padding: 9px 18px; border-radius: var(--radius-full); }
|
||||
|
||||
/* Стат-виджеты (glass) */
|
||||
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-4); margin-bottom: var(--space-8); }
|
||||
.stat-glass {
|
||||
background: var(--color-surface-glass); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255,255,255,.7); box-shadow: var(--shadow-glass); border-radius: var(--radius-xl);
|
||||
padding: var(--space-5); background-color: var(--color-bg-alt);
|
||||
}
|
||||
.stat-glass strong { display: block; font: var(--text-display-xl); font-family: var(--font-display); font-size: 36px; color: var(--color-ink-700); }
|
||||
.stat-glass span { font: var(--text-body); color: var(--color-ink-500); }
|
||||
|
||||
/* Табы разделов */
|
||||
.section-tabs { display: flex; gap: var(--space-2); margin-bottom: var(--space-5); border-bottom: 1px solid var(--color-border); }
|
||||
.section-tab { padding: 10px 4px; margin-right: var(--space-6); background: none; border: none; border-bottom: 2px solid transparent; font: var(--text-body-lg); font-weight: 600; color: var(--color-ink-500); }
|
||||
.section-tab.is-active { color: var(--color-ink-700); border-color: var(--color-accent-active); }
|
||||
|
||||
.toolbar-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-5); gap: var(--space-4); }
|
||||
.search-wrap { position: relative; width: 320px; }
|
||||
.search-wrap .icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--color-ink-400); }
|
||||
.search-wrap input { width: 100%; font: var(--text-body); padding: 9px 12px 9px 38px; border-radius: var(--radius-full); border: 1px solid var(--color-border); background: var(--color-surface); }
|
||||
.search-wrap input:focus { outline: none; border-color: var(--color-focus-ring); }
|
||||
|
||||
/* Таблица */
|
||||
.table-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); overflow: hidden; margin-bottom: var(--space-8); }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
thead th { text-align: left; background: var(--color-bg-alt); font: var(--text-caption); color: var(--color-ink-500); padding: 12px 20px; text-transform: uppercase; }
|
||||
tbody td { padding: 14px 20px; font: var(--text-body-lg); color: var(--color-ink-900); border-top: 1px solid var(--color-border); }
|
||||
tbody tr:hover { background: var(--color-bg-alt); }
|
||||
|
||||
.badge { display: inline-flex; align-items: center; gap: 6px; padding: 4px 12px; border-radius: var(--radius-full); font: var(--text-caption); white-space: nowrap; }
|
||||
.badge-free { background: var(--color-success-bg); color: var(--color-success); }
|
||||
.badge-busy { background: var(--color-danger-bg); color: var(--color-danger); }
|
||||
.badge-booked { background: var(--color-warning-bg); color: var(--color-warning); }
|
||||
.badge-locked { background: var(--color-bg-alt); color: var(--color-ink-700); }
|
||||
.badge-role-admin { background: var(--color-ink-700); color: #fff; }
|
||||
.badge-role-user { background: var(--color-bg-alt); color: var(--color-ink-500); }
|
||||
|
||||
.row-actions { display: flex; gap: 6px; }
|
||||
.icon-btn { width: 32px; height: 32px; border-radius: var(--radius-md); border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-ink-500); display:flex; align-items:center; justify-content:center; }
|
||||
.icon-btn:hover { border-color: var(--color-border-strong); color: var(--color-ink-900); }
|
||||
.icon-btn.danger:hover { border-color: var(--color-danger); color: var(--color-danger); background: var(--color-danger-bg); }
|
||||
|
||||
.user-cell { display: flex; align-items: center; gap: 10px; }
|
||||
.user-cell .avatar { width: 32px; height: 32px; font-size: 12px; }
|
||||
.user-cell div span { display: block; font: 13px var(--font-body); color: var(--color-ink-500); }
|
||||
|
||||
/* Секция рассылки */
|
||||
.invite-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); padding: var(--space-6); display: grid; grid-template-columns: 1fr 320px; gap: var(--space-8); }
|
||||
.invite-card h2 { font: var(--text-h2); margin: 0 0 4px; color: var(--color-ink-900); }
|
||||
.invite-card p.desc { font: var(--text-body); color: var(--color-ink-500); margin: 0 0 var(--space-5); }
|
||||
.invite-card textarea {
|
||||
width: 100%; min-height: 140px; font: var(--text-body-lg); color: var(--color-ink-900); font-family: var(--font-mono);
|
||||
padding: 14px; border-radius: var(--radius-md); border: 1px solid var(--color-border); background: var(--color-surface); resize: vertical;
|
||||
}
|
||||
.invite-card textarea:focus { outline: none; border-color: var(--color-focus-ring); }
|
||||
.invite-side { background: var(--color-bg-alt); border-radius: var(--radius-xl); padding: var(--space-5); }
|
||||
.invite-side h3 { font: var(--text-h3); margin: 0 0 var(--space-3); color: var(--color-ink-900); }
|
||||
.invite-side ul { margin: 0 0 var(--space-5); padding-left: 18px; font: var(--text-body); color: var(--color-ink-500); }
|
||||
.invite-side li { margin-bottom: 6px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<svg style="display:none">
|
||||
<symbol id="i-dashboard" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></symbol>
|
||||
<symbol id="i-video" viewBox="0 0 24 24"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="m22 8-6 4 6 4V8Z"/></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||||
<symbol id="i-users" viewBox="0 0 24 24"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></symbol>
|
||||
<symbol id="i-mail" viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m2 6 10 7 10-7"/></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></symbol>
|
||||
<symbol id="i-plus" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></symbol>
|
||||
<symbol id="i-pencil" viewBox="0 0 24 24"><path d="M17 3a2.83 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></symbol>
|
||||
<symbol id="i-trash" viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></symbol>
|
||||
<symbol id="i-send" viewBox="0 0 24 24"><path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/></symbol>
|
||||
<symbol id="i-check-circle" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></symbol>
|
||||
<symbol id="i-ban" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="4.9" y1="4.9" x2="19.1" y2="19.1"/></symbol>
|
||||
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></symbol>
|
||||
</svg>
|
||||
|
||||
<div class="app-shell">
|
||||
|
||||
<aside class="sidebar">
|
||||
<div class="brand-mark"><svg class="logo-mark" viewBox="0 0 32 32" fill="none" aria-hidden="true"><polygon points="21,16 18.5,20.3 13.5,20.3 11,16 13.5,11.7 18.5,11.7" fill="currentColor"/><circle cx="16" cy="16" r="13" stroke="currentColor" stroke-width="2.6" stroke-dasharray="12 6"/></svg> VidConf</div>
|
||||
<nav class="side-nav">
|
||||
<a class="side-link is-active" href="#rooms"><svg class="icon" style="width:18px;height:18px"><use href="#i-video"/></svg>Конференции</a>
|
||||
<a class="side-link" href="#closed"><svg class="icon" style="width:18px;height:18px"><use href="#i-lock"/></svg>Закрытые конференции</a>
|
||||
<a class="side-link" href="#users"><svg class="icon" style="width:18px;height:18px"><use href="#i-users"/></svg>Пользователи</a>
|
||||
<a class="side-link" href="#invites"><svg class="icon" style="width:18px;height:18px"><use href="#i-mail"/></svg>Рассылка приглашений</a>
|
||||
</nav>
|
||||
<div class="side-footer">
|
||||
<button class="avatar-btn"><span class="avatar">АИ</span><div><strong>Анна Иванова</strong><span>Администратор</span></div></button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main>
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Администрирование</h1>
|
||||
<p>Управление конференциями, доступом и пользователями VidConf</p>
|
||||
</div>
|
||||
<button class="btn btn-primary"><svg class="icon" style="width:16px;height:16px"><use href="#i-plus"/></svg>Добавить конференцию</button>
|
||||
</div>
|
||||
|
||||
<section class="stat-grid">
|
||||
<div class="stat-glass"><strong>100</strong><span>Всего конференций</span></div>
|
||||
<div class="stat-glass"><strong>12</strong><span>Активны сейчас</span></div>
|
||||
<div class="stat-glass"><strong>34</strong><span>Забронировано сегодня</span></div>
|
||||
<div class="stat-glass"><strong>248</strong><span>Пользователей</span></div>
|
||||
</section>
|
||||
|
||||
<div class="section-tabs">
|
||||
<button class="section-tab is-active">Конференции</button>
|
||||
<button class="section-tab">Закрытые конференции</button>
|
||||
<button class="section-tab">Пользователи</button>
|
||||
<button class="section-tab">Рассылка приглашений</button>
|
||||
</div>
|
||||
|
||||
<!-- ===================== КОНФЕРЕНЦИИ ===================== -->
|
||||
<div class="toolbar-row">
|
||||
<div class="search-wrap"><svg class="icon"><use href="#i-search"/></svg><input type="text" placeholder="Поиск конференции…"></div>
|
||||
<span style="font:14px var(--font-body); color:var(--color-ink-500)">Показано 6 из 100</span>
|
||||
</div>
|
||||
|
||||
<div class="table-card">
|
||||
<table>
|
||||
<thead><tr><th>Конференция</th><th>Вместимость</th><th>Статус</th><th>Тип</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Переговорная №01</td><td>8 мест</td><td><span class="badge badge-free"><svg class="icon" style="width:12px;height:12px"><use href="#i-check-circle"/></svg>Свободна</span></td><td>Открытая</td>
|
||||
<td><div class="row-actions"><button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:16px;height:16px"><use href="#i-pencil"/></svg></button><button class="icon-btn danger" aria-label="Удалить"><svg class="icon" style="width:16px;height:16px"><use href="#i-trash"/></svg></button></div></td></tr>
|
||||
<tr><td>Переговорная №02</td><td>10 мест</td><td><span class="badge badge-busy"><svg class="icon" style="width:12px;height:12px"><use href="#i-ban"/></svg>Занята</span></td><td>Открытая</td>
|
||||
<td><div class="row-actions"><button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:16px;height:16px"><use href="#i-pencil"/></svg></button><button class="icon-btn danger" aria-label="Удалить"><svg class="icon" style="width:16px;height:16px"><use href="#i-trash"/></svg></button></div></td></tr>
|
||||
<tr><td>Переговорная №04</td><td>12 мест</td><td><span class="badge badge-locked"><svg class="icon" style="width:12px;height:12px"><use href="#i-lock"/></svg>По паролю</span></td><td>Закрытая</td>
|
||||
<td><div class="row-actions"><button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:16px;height:16px"><use href="#i-pencil"/></svg></button><button class="icon-btn danger" aria-label="Удалить"><svg class="icon" style="width:16px;height:16px"><use href="#i-trash"/></svg></button></div></td></tr>
|
||||
<tr><td>Переговорная №42</td><td>16 мест</td><td><span class="badge badge-booked"><svg class="icon" style="width:12px;height:12px"><use href="#i-clock"/></svg>Забронирована</span></td><td>Открытая</td>
|
||||
<td><div class="row-actions"><button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:16px;height:16px"><use href="#i-pencil"/></svg></button><button class="icon-btn danger" aria-label="Удалить"><svg class="icon" style="width:16px;height:16px"><use href="#i-trash"/></svg></button></div></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ===================== ПОЛЬЗОВАТЕЛИ ===================== -->
|
||||
<div class="page-head" style="margin-bottom: var(--space-4)"><h2 style="font:var(--text-h2); color:var(--color-ink-900); margin:0">Пользователи</h2></div>
|
||||
<div class="table-card">
|
||||
<table>
|
||||
<thead><tr><th>Пользователь</th><th>Роль</th><th>Регистрация</th><th>Статус</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><div class="user-cell"><span class="avatar">АИ</span><div>Анна Иванова<span>a.ivanova@vidconf.dev</span></div></div></td>
|
||||
<td><span class="badge badge-role-admin">Администратор</span></td>
|
||||
<td>03.02.2026</td>
|
||||
<td><span class="badge badge-free"><svg class="icon" style="width:12px;height:12px"><use href="#i-check-circle"/></svg>Активен</span></td>
|
||||
<td><div class="row-actions"><button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:16px;height:16px"><use href="#i-pencil"/></svg></button></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div class="user-cell"><span class="avatar">МК</span><div>Мария Кузнецова<span>m.kuznetsova@company.ru</span></div></div></td>
|
||||
<td><span class="badge badge-role-user">Пользователь</span></td>
|
||||
<td>15.07.2026</td>
|
||||
<td><span class="badge badge-booked"><svg class="icon" style="width:12px;height:12px"><use href="#i-clock"/></svg>Ждёт подтверждения</span></td>
|
||||
<td><div class="row-actions"><button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:16px;height:16px"><use href="#i-pencil"/></svg></button><button class="icon-btn danger" aria-label="Удалить"><svg class="icon" style="width:16px;height:16px"><use href="#i-trash"/></svg></button></div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ===================== РАССЫЛКА ПРИГЛАШЕНИЙ ===================== -->
|
||||
<div class="page-head" style="margin-bottom: var(--space-4)"><h2 style="font:var(--text-h2); color:var(--color-ink-900); margin:0">Рассылка приглашений</h2></div>
|
||||
<div class="invite-card">
|
||||
<div>
|
||||
<h2>Пригласить новых пользователей</h2>
|
||||
<p class="desc">Каждый адрес получит письмо со ссылкой регистрации, действительной 7 дней.</p>
|
||||
<textarea placeholder="i.sokolov@company.ru o.panina@company.ru v.sedykh@company.ru">i.sokolov@company.ru
|
||||
o.panina@company.ru
|
||||
v.sedykh@company.ru</textarea>
|
||||
<button class="btn btn-primary" style="margin-top: var(--space-4)"><svg class="icon" style="width:16px;height:16px"><use href="#i-send"/></svg>Отправить приглашения (3)</button>
|
||||
</div>
|
||||
<div class="invite-side">
|
||||
<h3>Последняя рассылка</h3>
|
||||
<ul>
|
||||
<li>28.06.2026 · 12 приглашений</li>
|
||||
<li>9 приняли, 3 ожидают</li>
|
||||
</ul>
|
||||
<button class="btn btn-secondary" style="width:100%; justify-content:center">Посмотреть журнал</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
330
design/mockups/auth.html
Normal file
330
design/mockups/auth.html
Normal file
@@ -0,0 +1,330 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>VidConf — Вход / Регистрация / Подтверждение email</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../tokens.css">
|
||||
<style>
|
||||
html { min-width: 1280px; }
|
||||
body { min-height: 100vh; }
|
||||
|
||||
.layout { display: flex; min-height: 100vh; }
|
||||
|
||||
/* ---------- Брендовая панель ---------- */
|
||||
.brand-panel {
|
||||
width: 42%;
|
||||
background: linear-gradient(160deg, #EDEFFA 0%, #F1F1F1 55%, #F7F7F8 100%);
|
||||
padding: var(--space-12) var(--space-12);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-right: 1px solid var(--color-border);
|
||||
}
|
||||
.brand-panel::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -120px; bottom: -120px;
|
||||
width: 380px; height: 380px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle at 30% 30%, rgba(212,242,227,.45), rgba(212,242,227,0) 70%);
|
||||
}
|
||||
.brand-mark {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font: var(--text-h3);
|
||||
color: var(--color-ink-700);
|
||||
z-index: 1;
|
||||
}
|
||||
.brand-mark .logo-mark { width: 22px; height: 22px; flex-shrink: 0; }
|
||||
|
||||
.brand-copy { z-index: 1; }
|
||||
.eyebrow {
|
||||
font: var(--text-caption);
|
||||
letter-spacing: .08em;
|
||||
color: var(--color-ink-400);
|
||||
margin: 0 0 var(--space-4);
|
||||
}
|
||||
.brand-headline {
|
||||
font: var(--text-display-lg);
|
||||
font-family: var(--font-display);
|
||||
color: var(--color-ink-700);
|
||||
margin: 0 0 var(--space-4);
|
||||
max-width: 460px;
|
||||
}
|
||||
.brand-sub {
|
||||
font: var(--text-body-lg);
|
||||
color: var(--color-ink-500);
|
||||
max-width: 420px;
|
||||
margin: 0;
|
||||
}
|
||||
.brand-stats { display: flex; gap: var(--space-4); z-index: 1; margin-top: var(--space-8); }
|
||||
.stat-glass {
|
||||
background: var(--color-surface-glass);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255,255,255,.7);
|
||||
box-shadow: var(--shadow-glass);
|
||||
border-radius: var(--radius-xl);
|
||||
padding: var(--space-4) var(--space-5);
|
||||
min-width: 140px;
|
||||
}
|
||||
.stat-glass strong { display: block; font: var(--text-h1); font-family: var(--font-display); color: var(--color-ink-700); }
|
||||
.stat-glass span { font: var(--text-caption); color: var(--color-ink-500); text-transform: none; letter-spacing: normal; }
|
||||
|
||||
/* ---------- Форма-панель ---------- */
|
||||
.form-panel {
|
||||
width: 58%;
|
||||
padding: var(--space-12) var(--space-16);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-16);
|
||||
}
|
||||
.screen-block { max-width: 440px; }
|
||||
.screen-label {
|
||||
font: var(--text-caption);
|
||||
color: var(--color-ink-400);
|
||||
margin: 0 0 var(--space-3);
|
||||
}
|
||||
.screen-label b { color: var(--color-ink-700); }
|
||||
.screen-title { font: var(--text-h1); color: var(--color-ink-900); margin: 0 0 var(--space-8); }
|
||||
|
||||
.divider { border: none; border-top: 1px solid var(--color-border); max-width: 720px; margin: 0; }
|
||||
|
||||
.tabs { display: flex; gap: var(--space-2); margin-bottom: var(--space-6); background: var(--color-bg-alt); padding: 4px; border-radius: var(--radius-full); width: fit-content; }
|
||||
.tab { padding: 8px 20px; border-radius: var(--radius-full); border: none; background: transparent; font: var(--text-body); color: var(--color-ink-500); }
|
||||
.tab.is-active { background: var(--color-surface); color: var(--color-ink-700); font-weight: 600; box-shadow: var(--shadow-sm); }
|
||||
|
||||
.field { margin-bottom: var(--space-5); }
|
||||
.field label { display: block; font: var(--text-body); font-weight: 600; color: var(--color-ink-900); margin-bottom: var(--space-2); }
|
||||
.input-wrap { position: relative; }
|
||||
.input-wrap .icon { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--color-ink-400); }
|
||||
.input-wrap input {
|
||||
width: 100%;
|
||||
font: var(--text-body-lg);
|
||||
color: var(--color-ink-900);
|
||||
padding: 11px 14px 11px 42px;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-surface);
|
||||
outline: none;
|
||||
}
|
||||
.input-wrap input::placeholder { color: var(--color-ink-300); }
|
||||
.input-wrap input:hover { border-color: var(--color-border-strong); }
|
||||
.input-wrap input:focus { border-color: var(--color-focus-ring); box-shadow: 0 0 0 3px rgba(59,77,149,.12); }
|
||||
.input-wrap .toggle-eye { left: auto; right: 14px; background: none; border: none; padding: 0; cursor: pointer; }
|
||||
|
||||
.field.has-error input { border-color: var(--color-danger); box-shadow: 0 0 0 3px rgba(199,54,79,.1); }
|
||||
.error-msg { display: flex; align-items: center; gap: 6px; margin-top: var(--space-2); font: var(--text-body); color: var(--color-danger); }
|
||||
.hint-msg { margin-top: var(--space-2); font: var(--text-body); color: var(--color-ink-500); }
|
||||
|
||||
.field-disabled input { background: var(--color-bg-alt); color: var(--color-ink-300); cursor: not-allowed; }
|
||||
|
||||
.row-between { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-6); }
|
||||
.checkbox-row { display: flex; align-items: center; gap: 8px; font: var(--text-body); color: var(--color-ink-500); }
|
||||
.link { color: var(--color-ink-700); font-weight: 600; text-decoration: none; }
|
||||
.link:hover { text-decoration: underline; }
|
||||
|
||||
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; border: none; cursor: pointer; font-family: var(--font-body); transition: background var(--transition-fast), box-shadow var(--transition-fast); }
|
||||
.btn-primary {
|
||||
width: 100%;
|
||||
background: var(--color-accent);
|
||||
color: var(--color-accent-text);
|
||||
font: var(--text-body-lg);
|
||||
font-weight: 700;
|
||||
padding: 13px 24px;
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
.btn-primary:hover { background: var(--color-accent-hover); box-shadow: var(--shadow-md); }
|
||||
.btn-primary:active { background: var(--color-accent-active); }
|
||||
.btn-primary[disabled] { background: rgba(166,174,203,.4); color: var(--color-surface); cursor: not-allowed; }
|
||||
|
||||
.btn-secondary {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
color: var(--color-ink-700);
|
||||
border: 1.5px solid var(--color-border-pill);
|
||||
font: var(--text-body-lg);
|
||||
font-weight: 600;
|
||||
padding: 12px 24px;
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
.btn-secondary:hover { background: var(--color-bg-alt); border-color: var(--color-ink-700); }
|
||||
|
||||
.oauth-row { display: flex; gap: var(--space-3); margin-top: var(--space-5); }
|
||||
.oauth-row .btn-secondary { flex: 1; }
|
||||
|
||||
.separator { display: flex; align-items: center; gap: 12px; color: var(--color-ink-400); font: var(--text-caption); margin: var(--space-6) 0; text-transform: none; letter-spacing: normal; }
|
||||
.separator::before, .separator::after { content: ""; flex: 1; height: 1px; background: var(--color-border); }
|
||||
|
||||
/* ---------- Подтверждение email ---------- */
|
||||
.confirm-card {
|
||||
max-width: 440px;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-2xl);
|
||||
padding: var(--space-8);
|
||||
text-align: left;
|
||||
}
|
||||
.confirm-icon {
|
||||
width: 56px; height: 56px; border-radius: var(--radius-full);
|
||||
background: var(--color-success-bg); color: var(--color-success);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
.confirm-icon .icon { width: 28px; height: 28px; }
|
||||
.confirm-card h3 { font: var(--text-h2); color: var(--color-ink-900); margin: 0 0 var(--space-2); }
|
||||
.confirm-card p { font: var(--text-body-lg); color: var(--color-ink-500); margin: 0 0 var(--space-6); }
|
||||
.confirm-card .mail-chip {
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
background: var(--color-bg-alt); border-radius: var(--radius-full);
|
||||
padding: 6px 14px; font: var(--text-mono-sm); color: var(--color-ink-700);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
/* pending confirmation state variant */
|
||||
.confirm-card.pending .confirm-icon { background: var(--color-warning-bg); color: var(--color-warning); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<svg style="display:none">
|
||||
<symbol id="i-mail" viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m2 6 10 7 10-7"/></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||||
<symbol id="i-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M6 21v-2a6 6 0 0 1 12 0v2"/></symbol>
|
||||
<symbol id="i-eye" viewBox="0 0 24 24"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></symbol>
|
||||
<symbol id="i-eye-off" viewBox="0 0 24 24"><path d="M3 3l18 18"/><path d="M10.58 10.58a2 2 0 0 0 2.83 2.83"/><path d="M9.88 5.09A9.77 9.77 0 0 1 12 5c6.5 0 10 7 10 7a13.16 13.16 0 0 1-3.06 4.06M6.1 6.1A13.16 13.16 0 0 0 2 12s3.5 7 10 7a9.77 9.77 0 0 0 4.9-1.3"/></symbol>
|
||||
<symbol id="i-check-circle" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></symbol>
|
||||
<symbol id="i-arrow-right" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></symbol>
|
||||
<symbol id="i-alert" viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></symbol>
|
||||
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></symbol>
|
||||
</svg>
|
||||
|
||||
<div class="layout">
|
||||
|
||||
<!-- ===================== БРЕНД-ПАНЕЛЬ ===================== -->
|
||||
<aside class="brand-panel">
|
||||
<div class="brand-mark"><svg class="logo-mark" viewBox="0 0 32 32" fill="none" aria-hidden="true"><polygon points="21,16 18.5,20.3 13.5,20.3 11,16 13.5,11.7 18.5,11.7" fill="currentColor"/><circle cx="16" cy="16" r="13" stroke="currentColor" stroke-width="2.6" stroke-dasharray="12 6"/></svg> VidConf</div>
|
||||
<div class="brand-copy">
|
||||
<p class="eyebrow">[ SELF-HOSTED ВИДЕОКОНФЕРЕНЦИИ ]</p>
|
||||
<h1 class="brand-headline">Ваши встречи.<br>Ваша инфраструктура.</h1>
|
||||
<p class="brand-sub">Видео, записи и AI-саммари обрабатываются на ваших серверах - данные не уходят
|
||||
за пределы вашей инфраструктуры. Гости подключаются по ссылке или номеру, без регистрации.</p>
|
||||
<div class="brand-stats">
|
||||
<div class="stat-glass"><strong>100%</strong><span>данных - в вашем контуре</span></div>
|
||||
<div class="stat-glass"><strong>AI-саммари</strong><span>на почту после встречи</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint-msg" style="z-index:1">© 2026 VidConf · self-hosted</p>
|
||||
</aside>
|
||||
|
||||
<!-- ===================== ФОРМА: ВХОД / РЕГИСТРАЦИЯ ===================== -->
|
||||
<main class="form-panel">
|
||||
|
||||
<section class="screen-block">
|
||||
<p class="screen-label">Экран: <b>вход в аккаунт</b></p>
|
||||
<div class="tabs" role="tablist" aria-label="Способ авторизации">
|
||||
<button class="tab is-active" role="tab" aria-selected="true">Вход</button>
|
||||
<button class="tab" role="tab" aria-selected="false">Регистрация</button>
|
||||
</div>
|
||||
|
||||
<form>
|
||||
<div class="field">
|
||||
<label for="login-email">Рабочая почта</label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-mail"/></svg>
|
||||
<input id="login-email" type="email" placeholder="you@company.ru" value="a.ivanova@vidconf.dev">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field has-error">
|
||||
<label for="login-pass">Пароль</label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-lock"/></svg>
|
||||
<input id="login-pass" type="password" placeholder="••••••••" value="wrongpass">
|
||||
<button type="button" class="toggle-eye" aria-label="Показать пароль">
|
||||
<svg class="icon"><use href="#i-eye"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<p class="error-msg"><svg class="icon" style="width:16px;height:16px"><use href="#i-alert"/></svg> Неверный email или пароль</p>
|
||||
</div>
|
||||
|
||||
<div class="row-between">
|
||||
<label class="checkbox-row"><input type="checkbox" checked> Запомнить меня</label>
|
||||
<a href="#" class="link">Забыли пароль?</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Войти <svg class="icon"><use href="#i-arrow-right"/></svg></button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<section class="screen-block">
|
||||
<p class="screen-label">Экран: <b>регистрация</b></p>
|
||||
<h2 class="screen-title">Создать аккаунт</h2>
|
||||
<form>
|
||||
<div class="field">
|
||||
<label for="reg-name">Имя и фамилия</label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-user"/></svg>
|
||||
<input id="reg-name" type="text" placeholder="Мария Кузнецова">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="reg-email">Рабочая почта</label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-mail"/></svg>
|
||||
<input id="reg-email" type="email" placeholder="you@company.ru">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="reg-pass">Пароль</label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-lock"/></svg>
|
||||
<input id="reg-pass" type="password" placeholder="Минимум 8 символов">
|
||||
<button type="button" class="toggle-eye" aria-label="Скрыть пароль">
|
||||
<svg class="icon"><use href="#i-eye-off"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<p class="hint-msg">Минимум 8 символов, буквы и цифры</p>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" style="margin-top:8px">Зарегистрироваться</button>
|
||||
<div class="separator">или</div>
|
||||
<div class="field field-disabled" style="margin-bottom:0">
|
||||
<label for="reg-invite">Код приглашения (недоступно без администратора)</label>
|
||||
<div class="input-wrap">
|
||||
<input id="reg-invite" type="text" placeholder="INVITE-XXXX-XXXX" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<section class="screen-block">
|
||||
<p class="screen-label">Экран: <b>подтверждение email</b></p>
|
||||
|
||||
<div class="confirm-card" style="margin-bottom: var(--space-6);">
|
||||
<div class="confirm-icon"><svg class="icon"><use href="#i-check-circle"/></svg></div>
|
||||
<h3>Почта подтверждена</h3>
|
||||
<p>Аккаунт <strong>a.ivanova@vidconf.dev</strong> активирован. Теперь
|
||||
доступно бронирование переговорных и вход в конференции.</p>
|
||||
<button class="btn btn-primary">Перейти в лобби <svg class="icon"><use href="#i-arrow-right"/></svg></button>
|
||||
</div>
|
||||
|
||||
<div class="confirm-card pending">
|
||||
<div class="confirm-icon"><svg class="icon"><use href="#i-clock"/></svg></div>
|
||||
<h3>Подтвердите почту</h3>
|
||||
<p>Мы отправили письмо со ссылкой на <span class="mail-chip"><svg class="icon" style="width:14px;height:14px"><use href="#i-mail"/></svg>m.kuznetsova@company.ru</span></p>
|
||||
<button class="btn btn-secondary">Отправить письмо повторно</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
507
design/mockups/calendar.html
Normal file
507
design/mockups/calendar.html
Normal file
@@ -0,0 +1,507 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>VidConf — Календарь конференций</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../tokens.css">
|
||||
<style>
|
||||
/*
|
||||
* Календарь — менеджер ЗАПЛАНИРОВАННЫХ КОНФЕРЕНЦИЙ (фаза R1, пивот),
|
||||
* не бронирование комнат: комнат больше нет, поэтому легенда
|
||||
* «занято/выбрано» и привязка к конкретной переговорной убраны.
|
||||
* Слева — сетка недели (5 дневных колонок) со списком конференций по дням,
|
||||
* справа — карточка создания со «Закрепить постоянную конференцию»
|
||||
* (раскрывает 4 варианта повторения). Ниже — доп. состояния:
|
||||
* редактирование закреплённой и отмена вхождения/всех повторений.
|
||||
* Интерактивность toggle/recurrence — чистый CSS (:has()), без JS,
|
||||
* по образцу .switch в исходном calendar.html (входное состояние — CSS).
|
||||
*/
|
||||
|
||||
html { min-width: 1280px; }
|
||||
|
||||
.topbar {
|
||||
position: sticky; top: 0; z-index: 20;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: var(--space-4) var(--space-8);
|
||||
background: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.brand-mark { display: inline-flex; align-items: center; gap: 8px; font: var(--text-h3); color: var(--color-ink-700); }
|
||||
.brand-mark .logo-mark { width: 22px; height: 22px; flex-shrink: 0; }
|
||||
.topbar-right { display: flex; align-items: center; gap: var(--space-5); }
|
||||
.nav-link { font: var(--text-body); font-weight: 600; color: var(--color-ink-500); text-decoration: none; }
|
||||
.nav-link.is-active { color: var(--color-ink-700); }
|
||||
.avatar-btn { display: flex; align-items: center; gap: 8px; background: var(--color-bg-alt); border: 1px solid var(--color-border); border-radius: var(--radius-full); padding: 6px 14px 6px 6px; }
|
||||
.avatar { width: 28px; height: 28px; border-radius: 50%; background: var(--color-ink-700); color: #fff; display:flex; align-items:center; justify-content:center; font: var(--text-caption); text-transform: none; letter-spacing: 0; }
|
||||
/* Каскад-баг гейта (2026-07-16, 3-я итерация): было `.avatar-btn span`
|
||||
(0,1,1) — перебивало `.avatar { color:#fff }` (0,1,0), инициалы
|
||||
рендерились тёмным ink-900 на индиго-заливке (нечитаемо). Селектор имени
|
||||
сделан адресным — задевает только `.user-name`, не пересекается с
|
||||
`.avatar` ни при каком порядке правил. См. DESIGN_SYSTEM.md §1.5. */
|
||||
.avatar-btn .user-name { font: var(--text-body); font-weight: 600; color: var(--color-ink-900); }
|
||||
|
||||
main { padding: var(--space-8); max-width: 1440px; margin: 0 auto; }
|
||||
|
||||
.page-head { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: var(--space-6); }
|
||||
.page-head h1 { font: var(--text-display-lg); font-family: var(--font-display); color: var(--color-ink-700); margin: 0 0 6px; }
|
||||
.page-head p { font: var(--text-body-lg); color: var(--color-ink-500); margin: 0; max-width: 560px; }
|
||||
|
||||
.btn { display: inline-flex; align-items: center; gap: 8px; border: none; cursor: pointer; font-family: var(--font-body); white-space: nowrap; transition: background var(--transition-fast), box-shadow var(--transition-fast); }
|
||||
.btn-primary { background: var(--color-accent); color: var(--color-accent-text); font: var(--text-body); font-weight: 700; padding: 11px 22px; border-radius: var(--radius-full); }
|
||||
.btn-primary:hover { background: var(--color-accent-hover); box-shadow: var(--shadow-md); }
|
||||
.btn-primary:active { background: var(--color-accent-active); }
|
||||
.btn-primary:disabled { background: rgba(166,174,203,.35); color: var(--color-surface); cursor: not-allowed; }
|
||||
.btn-secondary { background: transparent; color: var(--color-ink-700); border: 1.5px solid var(--color-border-pill); font: var(--text-body); font-weight: 600; padding: 9px 18px; border-radius: var(--radius-full); }
|
||||
.btn-secondary:hover { background: var(--color-bg-alt); }
|
||||
.btn-danger { background: var(--color-danger); color: #fff; font: var(--text-body); font-weight: 700; padding: 9px 18px; border-radius: var(--radius-full); }
|
||||
.btn-danger:hover { filter: brightness(0.92); }
|
||||
.btn-text { background: none; border: none; color: var(--color-danger); font: var(--text-body); font-weight: 600; padding: 4px; }
|
||||
.btn-text:hover { text-decoration: underline; }
|
||||
.btn.full { width: 100%; justify-content: center; }
|
||||
:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
|
||||
|
||||
/* ---------- Основной грид: неделя + карточка создания ---------- */
|
||||
.layout-grid { display: grid; grid-template-columns: 1fr 400px; gap: var(--space-6); align-items: start; }
|
||||
|
||||
.week-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); padding: var(--space-6); }
|
||||
.week-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-5); }
|
||||
.date-nav { display: flex; align-items: center; gap: var(--space-3); }
|
||||
.date-nav button { width: 34px; height: 34px; border-radius: 50%; border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-ink-500); display:flex; align-items:center; justify-content:center; }
|
||||
.date-nav button:hover { border-color: var(--color-border-strong); color: var(--color-ink-900); }
|
||||
.date-nav span { font: var(--text-h3); color: var(--color-ink-900); min-width: 200px; text-align: center; }
|
||||
.view-toggle { display: flex; gap: 4px; background: var(--color-bg-alt); padding: 4px; border-radius: var(--radius-full); }
|
||||
.view-toggle button { padding: 7px 16px; border-radius: var(--radius-full); border: none; background: transparent; font: var(--text-body); font-weight: 600; color: var(--color-ink-500); }
|
||||
.view-toggle button.is-active { background: var(--color-surface); color: var(--color-ink-700); box-shadow: var(--shadow-sm); }
|
||||
|
||||
.week-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--space-4); }
|
||||
.day-col { background: var(--color-bg-alt); border-radius: var(--radius-lg); padding: var(--space-3); display: flex; flex-direction: column; gap: var(--space-2); min-height: 460px; }
|
||||
.day-col.is-today { background: rgba(212,242,227,.16); }
|
||||
.day-col-head { text-align: center; padding-bottom: var(--space-2); margin-bottom: var(--space-1); border-bottom: 1px solid var(--color-border); }
|
||||
.day-col-head .dow { display: block; font: var(--text-caption); color: var(--color-ink-500); }
|
||||
.day-col-head .dom { display: block; font: var(--text-h3); color: var(--color-ink-900); margin-top: 2px; }
|
||||
.day-col.is-today .day-col-head .dom { color: var(--color-ink-700); }
|
||||
|
||||
.conf-chip { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-3); display: flex; flex-direction: column; gap: 4px; cursor: pointer; transition: box-shadow var(--transition-fast), border-color var(--transition-fast); text-align: left; }
|
||||
.conf-chip:hover { box-shadow: var(--shadow-sm); border-color: var(--color-border-strong); }
|
||||
.chip-time { display: flex; align-items: center; gap: 4px; font: var(--text-caption); color: var(--color-ink-500); text-transform: none; letter-spacing: normal; }
|
||||
.chip-title { display: flex; align-items: center; gap: 6px; font: var(--text-body); font-weight: 700; color: var(--color-ink-900); }
|
||||
.chip-meta { display: flex; align-items: center; gap: 10px; font: 12px var(--font-body); color: var(--color-ink-500); }
|
||||
.chip-meta .meta-item { display: flex; align-items: center; gap: 4px; }
|
||||
|
||||
.conf-badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px; border-radius: var(--radius-full); font: var(--text-caption); font-size: 11px; width: fit-content; }
|
||||
.conf-badge.scheduled { background: var(--color-warning-bg); color: var(--color-warning); }
|
||||
.conf-badge.pinned { background: var(--color-bg-alt); color: var(--color-ink-700); border: 1px solid var(--color-border-strong); }
|
||||
.conf-badge.live { background: var(--color-success-bg); color: var(--color-success); }
|
||||
.conf-badge.cancelled { background: var(--color-danger-bg); color: var(--color-danger); }
|
||||
|
||||
.conf-chip.is-cancelled { opacity: .6; }
|
||||
.conf-chip.is-cancelled .chip-title { text-decoration: line-through; color: var(--color-ink-500); }
|
||||
|
||||
.day-empty { flex: 1; display: flex; align-items: center; justify-content: center; text-align: center; font: var(--text-body); color: var(--color-ink-300); padding: var(--space-4) 0; }
|
||||
|
||||
/* ---------- Карточка создания / редактирования ---------- */
|
||||
.create-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); padding: var(--space-6); position: sticky; top: 96px; }
|
||||
.create-card h2 { font: var(--text-h2); color: var(--color-ink-900); margin: 0 0 var(--space-5); display: flex; align-items: center; gap: 8px; }
|
||||
|
||||
.field { margin-bottom: var(--space-5); }
|
||||
.field label { display: flex; align-items: center; gap: 6px; font: var(--text-body); font-weight: 600; color: var(--color-ink-900); margin-bottom: var(--space-2); }
|
||||
.field input, .field textarea, .field select {
|
||||
width: 100%; font: var(--text-body-lg); color: var(--color-ink-900);
|
||||
padding: 10px 14px; border-radius: var(--radius-md); border: 1px solid var(--color-border);
|
||||
background: var(--color-surface);
|
||||
}
|
||||
.field input:focus, .field textarea:focus, .field select:focus { outline: none; border-color: var(--color-focus-ring); box-shadow: 0 0 0 3px rgba(59,77,149,.12); }
|
||||
.field-row { display: flex; gap: var(--space-3); }
|
||||
.field-row .field { flex: 1; }
|
||||
.field-hint { font: 12px var(--font-body); color: var(--color-ink-500); margin: 6px 2px 0; }
|
||||
|
||||
/* Тумблер (используется и для «закрытая по паролю», и для «закрепить») */
|
||||
.toggle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-3) 0; border-top: 1px solid var(--color-border); }
|
||||
.toggle-copy strong { display: block; font: var(--text-body); color: var(--color-ink-900); }
|
||||
.toggle-copy span { font: 12px var(--font-body); color: var(--color-ink-500); }
|
||||
.switch { position: relative; width: 44px; height: 24px; flex-shrink: 0; }
|
||||
.switch input { opacity: 0; width: 0; height: 0; }
|
||||
.slider { position: absolute; inset: 0; background: var(--color-border-strong); border-radius: var(--radius-full); transition: background var(--transition-fast); cursor: pointer; }
|
||||
.slider::before { content:""; position:absolute; width:18px; height:18px; left:3px; top:3px; background:#fff; border-radius:50%; transition: transform var(--transition-fast); }
|
||||
.switch input:checked + .slider { background: var(--color-accent-active); }
|
||||
.switch input:checked + .slider::before { transform: translateX(20px); }
|
||||
.switch input:focus-visible + .slider { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
|
||||
|
||||
.password-block { display: none; margin-top: var(--space-4); }
|
||||
.toggle-block:has(#pw-toggle:checked) .password-block { display: block; }
|
||||
|
||||
/* ---------- Блок повторений (recurrence) ---------- */
|
||||
.pin-block .recurrence-panel { display: none; }
|
||||
.pin-block:has(input[type="checkbox"]:checked) .recurrence-panel {
|
||||
display: flex; flex-direction: column; gap: var(--space-2);
|
||||
margin-top: var(--space-4); padding-top: var(--space-4);
|
||||
border-top: 1px dashed var(--color-border);
|
||||
}
|
||||
|
||||
.recurrence-option-row {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 10px 12px; border: 1px solid var(--color-border); border-radius: var(--radius-md);
|
||||
cursor: pointer; font: var(--text-body); font-weight: 600; color: var(--color-ink-900);
|
||||
transition: border-color var(--transition-fast), background var(--transition-fast);
|
||||
}
|
||||
.recurrence-option-row input[type="radio"] { width: 16px; height: 16px; accent-color: var(--color-ink-700); flex-shrink: 0; }
|
||||
.recurrence-option:has(input:checked) .recurrence-option-row { border-color: var(--color-ink-700); background: var(--color-bg-alt); }
|
||||
|
||||
.recurrence-sub { display: none; padding: var(--space-3) 12px 4px 38px; }
|
||||
.recurrence-option:has(input:checked) .recurrence-sub { display: block; }
|
||||
|
||||
.weekday-toggles { display: flex; gap: 6px; flex-wrap: wrap; }
|
||||
.weekday-chip {
|
||||
width: 34px; height: 34px; border-radius: 50%;
|
||||
border: 1.5px solid var(--color-border); background: var(--color-surface);
|
||||
font: var(--text-caption); color: var(--color-ink-500);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.weekday-chip.is-selected { background: var(--color-ink-700); border-color: var(--color-ink-700); color: #fff; }
|
||||
|
||||
.recurrence-hint { font: 12px var(--font-body); color: var(--color-ink-500); margin-top: 8px; }
|
||||
|
||||
/* ---------- Доп. состояния (ниже основного грида) ---------- */
|
||||
.states-section { margin-top: var(--space-10); }
|
||||
.states-head { margin-bottom: var(--space-5); }
|
||||
.states-head h2 { font: var(--text-h1); color: var(--color-ink-900); margin: 0 0 4px; }
|
||||
.states-head p { font: var(--text-body-lg); color: var(--color-ink-500); margin: 0; }
|
||||
.states-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-6); align-items: start; }
|
||||
|
||||
.state-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); padding: var(--space-6); }
|
||||
.screen-label { font: var(--text-caption); color: var(--color-ink-400); margin: 0 0 var(--space-4); }
|
||||
.screen-label b { color: var(--color-ink-700); }
|
||||
|
||||
.next-occurrence { display: flex; align-items: center; gap: 8px; background: var(--color-bg-alt); border-radius: var(--radius-md); padding: 10px 14px; font: var(--text-body); color: var(--color-ink-700); font-weight: 600; margin-bottom: var(--space-5); }
|
||||
|
||||
.btn-row { display: flex; gap: var(--space-3); margin-top: var(--space-5); }
|
||||
.btn-row .btn { flex: 1; justify-content: center; }
|
||||
.danger-link-row { text-align: center; margin-top: var(--space-4); }
|
||||
|
||||
.cancel-alert { display: flex; gap: 10px; background: var(--color-warning-bg); border: 1px solid rgba(181,138,38,.3); color: var(--color-warning); border-radius: var(--radius-md); padding: var(--space-4); font: var(--text-body); margin-bottom: var(--space-5); }
|
||||
.cancel-alert strong { display: block; margin-bottom: 2px; color: var(--color-ink-900); }
|
||||
.cancel-options { display: flex; flex-direction: column; gap: var(--space-3); }
|
||||
.cancel-options .btn { width: 100%; justify-content: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<svg style="display:none">
|
||||
<symbol id="i-chevron-left" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></symbol>
|
||||
<symbol id="i-chevron-right" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></symbol>
|
||||
<symbol id="i-calendar" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></symbol>
|
||||
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||||
<symbol id="i-alert" viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></symbol>
|
||||
<symbol id="i-plus" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></symbol>
|
||||
<symbol id="i-repeat" viewBox="0 0 24 24"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/></symbol>
|
||||
<symbol id="i-play" viewBox="0 0 24 24"><polygon points="6 3 20 12 6 21 6 3"/></symbol>
|
||||
<symbol id="i-users" viewBox="0 0 24 24"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></symbol>
|
||||
<symbol id="i-x" viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></symbol>
|
||||
</svg>
|
||||
|
||||
<header class="topbar">
|
||||
<div class="brand-mark"><svg class="logo-mark" viewBox="0 0 32 32" fill="none" aria-hidden="true"><polygon points="21,16 18.5,20.3 13.5,20.3 11,16 13.5,11.7 18.5,11.7" fill="currentColor"/><circle cx="16" cy="16" r="13" stroke="currentColor" stroke-width="2.6" stroke-dasharray="12 6"/></svg> VidConf</div>
|
||||
<nav class="topbar-right">
|
||||
<a href="lobby.html" class="nav-link">Лобби</a>
|
||||
<a href="calendar.html" class="nav-link is-active">Календарь</a>
|
||||
<a href="my-conferences.html" class="nav-link">Мои конференции</a>
|
||||
<a href="admin.html" class="nav-link">Админка</a>
|
||||
<button class="avatar-btn"><span class="avatar">АИ</span><span class="user-name">Анна Иванова</span></button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Календарь конференций</h1>
|
||||
<p>Запланируйте разовую встречу или закрепите постоянную конференцию с повторением — рабочая неделя 14–18 июля</p>
|
||||
</div>
|
||||
<button class="btn btn-primary"><svg class="icon" style="width:16px;height:16px"><use href="#i-plus"/></svg>Новая конференция</button>
|
||||
</div>
|
||||
|
||||
<div class="layout-grid">
|
||||
|
||||
<!-- ===================== СЕТКА КОНФЕРЕНЦИЙ ПО ДНЯМ ===================== -->
|
||||
<section class="week-card">
|
||||
<div class="week-toolbar">
|
||||
<div class="date-nav">
|
||||
<button aria-label="Предыдущая неделя"><svg class="icon" style="width:16px;height:16px"><use href="#i-chevron-left"/></svg></button>
|
||||
<span>14 — 18 июля 2026</span>
|
||||
<button aria-label="Следующая неделя"><svg class="icon" style="width:16px;height:16px"><use href="#i-chevron-right"/></svg></button>
|
||||
</div>
|
||||
<div class="view-toggle">
|
||||
<button class="is-active">Неделя</button>
|
||||
<button>Месяц</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="week-grid">
|
||||
|
||||
<div class="day-col">
|
||||
<div class="day-col-head"><span class="dow">ПН</span><span class="dom">14</span></div>
|
||||
<button class="conf-chip">
|
||||
<span class="conf-badge scheduled"><svg class="icon" style="width:11px;height:11px"><use href="#i-clock"/></svg>Запланирована</span>
|
||||
<span class="chip-time">09:00–09:15</span>
|
||||
<span class="chip-title">Дейли команды</span>
|
||||
<span class="chip-meta"><span class="meta-item"><svg class="icon" style="width:12px;height:12px"><use href="#i-users"/></svg>6</span></span>
|
||||
</button>
|
||||
<div class="day-empty">Свободный день</div>
|
||||
</div>
|
||||
|
||||
<div class="day-col is-today">
|
||||
<div class="day-col-head"><span class="dow">ВТ</span><span class="dom">15</span></div>
|
||||
<button class="conf-chip">
|
||||
<span class="conf-badge pinned"><svg class="icon" style="width:11px;height:11px"><use href="#i-repeat"/></svg>Закреплена</span>
|
||||
<span class="chip-time">11:00–11:45</span>
|
||||
<span class="chip-title">Ревью спринта</span>
|
||||
<span class="chip-meta"><span class="meta-item"><svg class="icon" style="width:12px;height:12px"><use href="#i-users"/></svg>8</span></span>
|
||||
</button>
|
||||
<button class="conf-chip">
|
||||
<span class="conf-badge live"><svg class="icon" style="width:11px;height:11px"><use href="#i-play"/></svg>Идёт сейчас</span>
|
||||
<span class="chip-time">14:00–…</span>
|
||||
<span class="chip-title"><svg class="icon" style="width:13px;height:13px;flex-shrink:0"><use href="#i-lock"/></svg>Синк по релизу 2.4</span>
|
||||
<span class="chip-meta"><span class="meta-item"><svg class="icon" style="width:12px;height:12px"><use href="#i-users"/></svg>4</span></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="day-col">
|
||||
<div class="day-col-head"><span class="dow">СР</span><span class="dom">16</span></div>
|
||||
<button class="conf-chip is-cancelled">
|
||||
<span class="conf-badge cancelled"><svg class="icon" style="width:11px;height:11px"><use href="#i-x"/></svg>Отменена</span>
|
||||
<span class="chip-time">10:00–10:30</span>
|
||||
<span class="chip-title">Собес: Frontend</span>
|
||||
</button>
|
||||
<div class="day-empty">Свободный день</div>
|
||||
</div>
|
||||
|
||||
<div class="day-col">
|
||||
<div class="day-col-head"><span class="dow">ЧТ</span><span class="dom">17</span></div>
|
||||
<button class="conf-chip">
|
||||
<span class="conf-badge pinned"><svg class="icon" style="width:11px;height:11px"><use href="#i-repeat"/></svg>Закреплена</span>
|
||||
<span class="chip-time">11:00–11:45</span>
|
||||
<span class="chip-title">Ревью спринта</span>
|
||||
<span class="chip-meta"><span class="meta-item"><svg class="icon" style="width:12px;height:12px"><use href="#i-users"/></svg>8</span></span>
|
||||
</button>
|
||||
<button class="conf-chip">
|
||||
<span class="conf-badge scheduled"><svg class="icon" style="width:11px;height:11px"><use href="#i-clock"/></svg>Запланирована</span>
|
||||
<span class="chip-time">15:00–15:30</span>
|
||||
<span class="chip-title">1:1 с руководителем</span>
|
||||
<span class="chip-meta"><span class="meta-item"><svg class="icon" style="width:12px;height:12px"><use href="#i-users"/></svg>2</span></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="day-col">
|
||||
<div class="day-col-head"><span class="dow">ПТ</span><span class="dom">18</span></div>
|
||||
<button class="conf-chip">
|
||||
<span class="conf-badge scheduled"><svg class="icon" style="width:11px;height:11px"><use href="#i-clock"/></svg>Запланирована</span>
|
||||
<span class="chip-time">12:00–13:00</span>
|
||||
<span class="chip-title"><svg class="icon" style="width:13px;height:13px;flex-shrink:0"><use href="#i-lock"/></svg>Итоги квартала</span>
|
||||
<span class="chip-meta"><span class="meta-item"><svg class="icon" style="width:12px;height:12px"><use href="#i-users"/></svg>14</span></span>
|
||||
</button>
|
||||
<div class="day-empty">Ещё нет конференций</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===================== КАРТОЧКА СОЗДАНИЯ (состояние: создание, пусто) ===================== -->
|
||||
<aside class="create-card" id="create-card">
|
||||
<h2><svg class="icon"><use href="#i-calendar"/></svg>Новая конференция</h2>
|
||||
|
||||
<form>
|
||||
<div class="field">
|
||||
<label>Название</label>
|
||||
<input type="text" placeholder="Например, Синк по релизу 2.4">
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<div class="field"><label>Дата</label><input type="text" value="21.07.2026"></div>
|
||||
<div class="field"><label>Время</label><input type="text" value="10:00–10:30"></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Участники (email через запятую)</label>
|
||||
<textarea rows="2" placeholder="i.sokolov@company.ru, m.kuznetsova@company.ru"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="toggle-block">
|
||||
<div class="toggle-row" style="border-top:none; padding-top:0">
|
||||
<div class="toggle-copy"><strong>Закрытая по паролю</strong><span>Вход только по паролю, включая гостей</span></div>
|
||||
<label class="switch"><input type="checkbox" id="pw-toggle"><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="password-block field">
|
||||
<label><svg class="icon" style="width:14px;height:14px"><use href="#i-lock"/></svg>Пароль конференции</label>
|
||||
<input type="password" placeholder="Минимум 6 символов">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pin-block">
|
||||
<div class="toggle-row">
|
||||
<div class="toggle-copy"><strong>Закрепить постоянную конференцию</strong><span>Сохранится и попадёт в «Мои конференции», не исчезнет после встречи</span></div>
|
||||
<label class="switch"><input type="checkbox" id="pin-toggle"><span class="slider"></span></label>
|
||||
</div>
|
||||
|
||||
<!-- Раскрывается по клику на тумблер выше (CSS :has(), без JS) -->
|
||||
<div class="recurrence-panel">
|
||||
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row">
|
||||
<input type="radio" name="recurrence" checked>
|
||||
По дням недели, еженедельно
|
||||
</label>
|
||||
<div class="recurrence-sub">
|
||||
<div class="weekday-toggles">
|
||||
<span class="weekday-chip">ПН</span>
|
||||
<span class="weekday-chip is-selected">ВТ</span>
|
||||
<span class="weekday-chip">СР</span>
|
||||
<span class="weekday-chip is-selected">ЧТ</span>
|
||||
<span class="weekday-chip">ПТ</span>
|
||||
<span class="weekday-chip">СБ</span>
|
||||
<span class="weekday-chip">ВС</span>
|
||||
</div>
|
||||
<p class="recurrence-hint">Ближайшие: вторник 21 июля, четверг 23 июля</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row">
|
||||
<input type="radio" name="recurrence">
|
||||
Раз в 2 недели
|
||||
</label>
|
||||
<div class="recurrence-sub">
|
||||
<div class="field-row">
|
||||
<div class="field" style="margin-bottom:0"><label>Начиная с</label><input type="text" value="21.07.2026"></div>
|
||||
<div class="field" style="margin-bottom:0"><label>День недели</label><select><option>Вторник</option><option>Среда</option></select></div>
|
||||
</div>
|
||||
<p class="recurrence-hint">Следующие: 4 августа, 18 августа</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row">
|
||||
<input type="radio" name="recurrence">
|
||||
Раз в месяц
|
||||
</label>
|
||||
<div class="recurrence-sub">
|
||||
<div class="field"><label>Число месяца</label><input type="text" value="31" inputmode="numeric"></div>
|
||||
<p class="recurrence-hint">Если в месяце нет такого числа — переносится на последний день месяца (напр. 28/29 февраля)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row">
|
||||
<input type="radio" name="recurrence">
|
||||
Каждые N дней
|
||||
</label>
|
||||
<div class="recurrence-sub">
|
||||
<div class="field-row">
|
||||
<div class="field" style="margin-bottom:0"><label>N (дней)</label><input type="text" value="10" inputmode="numeric"></div>
|
||||
<div class="field" style="margin-bottom:0"><label>Начиная с</label><input type="text" value="21.07.2026"></div>
|
||||
</div>
|
||||
<p class="recurrence-hint">Ближайшие: через 10, 20, 30 дней</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary full" style="margin-top: var(--space-5)">Создать конференцию</button>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ===================== ДОП. СОСТОЯНИЯ: РЕДАКТИРОВАНИЕ И ОТМЕНА ===================== -->
|
||||
<section class="states-section">
|
||||
<div class="states-head">
|
||||
<h2>Другие состояния карточки</h2>
|
||||
<p>Раскрытая карточка с закреплением, редактирование существующей закреплённой конференции и отмена вхождения/всех повторений.</p>
|
||||
</div>
|
||||
|
||||
<div class="states-grid">
|
||||
|
||||
<!-- Состояние: редактирование существующей закреплённой конференции -->
|
||||
<div class="state-card">
|
||||
<p class="screen-label">Состояние: <b>редактирование закреплённой конференции</b></p>
|
||||
|
||||
<div class="next-occurrence">
|
||||
<svg class="icon" style="width:16px;height:16px"><use href="#i-clock"/></svg>
|
||||
Следующее вхождение: вторник, 22 июля, 11:00
|
||||
</div>
|
||||
|
||||
<form>
|
||||
<div class="field"><label>Название</label><input type="text" value="Ревью спринта"></div>
|
||||
<div class="field-row">
|
||||
<div class="field"><label>Время</label><input type="text" value="11:00–11:45"></div>
|
||||
<div class="field"><label>Участники</label><input type="text" value="8 человек"></div>
|
||||
</div>
|
||||
|
||||
<div class="pin-block">
|
||||
<div class="toggle-row" style="border-top:none; padding-top:0">
|
||||
<div class="toggle-copy"><strong>Закреплена</strong><span>Повторяется по расписанию</span></div>
|
||||
<label class="switch"><input type="checkbox" id="pin-toggle-edit" checked><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="recurrence-panel">
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row">
|
||||
<input type="radio" name="recurrence-edit" checked>
|
||||
По дням недели, еженедельно
|
||||
</label>
|
||||
<div class="recurrence-sub">
|
||||
<div class="weekday-toggles">
|
||||
<span class="weekday-chip">ПН</span>
|
||||
<span class="weekday-chip is-selected">ВТ</span>
|
||||
<span class="weekday-chip">СР</span>
|
||||
<span class="weekday-chip is-selected">ЧТ</span>
|
||||
<span class="weekday-chip">ПТ</span>
|
||||
<span class="weekday-chip">СБ</span>
|
||||
<span class="weekday-chip">ВС</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row"><input type="radio" name="recurrence-edit">Раз в 2 недели</label>
|
||||
</div>
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row"><input type="radio" name="recurrence-edit">Раз в месяц</label>
|
||||
</div>
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row"><input type="radio" name="recurrence-edit">Каждые N дней</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-row">
|
||||
<button type="submit" class="btn btn-primary">Сохранить изменения</button>
|
||||
<button type="button" class="btn btn-secondary">Открепить</button>
|
||||
</div>
|
||||
<div class="danger-link-row">
|
||||
<button type="button" class="btn-text">Удалить конференцию навсегда</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Состояние: отмена одной / всех повторяющихся -->
|
||||
<div class="state-card">
|
||||
<p class="screen-label">Состояние: <b>отмена вхождения повторяющейся конференции</b></p>
|
||||
|
||||
<div class="cancel-alert">
|
||||
<svg class="icon" style="width:18px;height:18px;flex-shrink:0;margin-top:1px"><use href="#i-alert"/></svg>
|
||||
<div><strong>«Ревью спринта» — вторник, 22 июля, 11:00</strong>Это повторяющаяся конференция (по вторникам и четвергам). Что отменить?</div>
|
||||
</div>
|
||||
|
||||
<div class="cancel-options">
|
||||
<button class="btn btn-secondary"><svg class="icon" style="width:16px;height:16px"><use href="#i-x"/></svg>Отменить только это вхождение</button>
|
||||
<button class="btn btn-danger"><svg class="icon" style="width:16px;height:16px"><use href="#i-x"/></svg>Отменить все повторения</button>
|
||||
</div>
|
||||
<div class="danger-link-row">
|
||||
<button type="button" class="btn-text" style="color: var(--color-ink-500)">Оставить как есть</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
188
design/mockups/dark/README.md
Normal file
188
design/mockups/dark/README.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# VidConf — тёмные варианты оболочки
|
||||
|
||||
Обязательный референс для разработки.
|
||||
|
||||
Тёмная тема для экранов «оболочки» (auth/лобби/календарь/join/«мои
|
||||
конференции»/админка), реализована **поверх** дизайн-системы, а не взамен
|
||||
неё. `design/tokens.css` и `design/DESIGN_SYSTEM.md` — единственный
|
||||
источник истины по значениям (см. §0 `DESIGN_SYSTEM.md`: «светлая тема —
|
||||
оболочка (со светлым/тёмным вариантом), тёмная — комната конференции
|
||||
всегда»). Реализация во frontend — `frontend/src/hooks/useTheme.ts` и
|
||||
`frontend/src/components/ui/ThemeToggle.tsx` (см. `frontend/README.md`,
|
||||
раздел «Темы оболочки»).
|
||||
|
||||
## Что здесь
|
||||
|
||||
Разметка, контент и русские данные каждого файла — 1:1 копия
|
||||
соответствующего светлого макета из `design/mockups/`. Меняется только
|
||||
цветовая схема: вместо индиго-на-белом (`referens.png`) — графит-мята-роза-
|
||||
янтарь той же гаммы, что уже утверждена для комнаты конференции
|
||||
(`collor_ico.png`, токены `[data-theme="room"]` в `tokens.css`).
|
||||
|
||||
| Файл | Светлый прообраз | Экран |
|
||||
|---|---|---|
|
||||
| `login.html` | `../auth.html` | Вход / регистрация / подтверждение email |
|
||||
| `lobby.html` | `../lobby.html` | Лобби — хаб действий 2×2 |
|
||||
| `calendar.html` | `../calendar.html` | Календарь-менеджер конференций (+ создание/редактирование/отмена) |
|
||||
| `join.html` | `../join.html` | Подключение по ссылке/номеру → представиться (гость) → пароль |
|
||||
| `my-conferences.html` | `../my-conferences.html` | Мои конференции |
|
||||
| `admin.html` | `../admin.html` | Админка (конференции, пользователи, рассылка приглашений) |
|
||||
|
||||
`room.html` и `email-summary.html` в комплект не входят: комната уже тёмная
|
||||
(эталон гаммы), а письмо-саммари по правилам проекта всегда светлое
|
||||
(почтовые клиенты не гарантируют поддержку тёмной темы/CSS-переменных).
|
||||
Ссылки на «Войти»/«Создать» из `lobby.html`/`my-conferences.html` ведут на
|
||||
уже существующий `../room.html`; ссылка «Открыть саммари» — на
|
||||
`../email-summary.html`.
|
||||
|
||||
Каждый файл самодостаточен: встроенный `<style>`, инлайн-SVG-иконки
|
||||
(lucide-совместимые контуры), без CDN и внешних сетевых зависимостей —
|
||||
открывается двойным кликом. Единственная локальная зависимость —
|
||||
`<link rel="stylesheet" href="../../tokens.css">` (тот же приём, что и в
|
||||
светлых макетах: используются базовые не-цветовые токены — spacing, радиусы,
|
||||
типографика, тени-заготовки, шрифты).
|
||||
|
||||
## Переключатель темы
|
||||
|
||||
В шапке каждого макета — сегментированный переключатель ☀ / 🌙 (иконки
|
||||
`Sun`/`Moon`, lucide-стиль), «Тёмная» — активный сегмент. Элемент
|
||||
статичный (демонстрирует точку входа в реальный toggle, не переключает
|
||||
тему по клику в макете) и размещён:
|
||||
- `login.html` — плавающей плашкой в правом верхнем углу (у экрана нет
|
||||
общего топбара, панель бренда/форма — раздельные колонки);
|
||||
- `lobby.html`, `calendar.html`, `my-conferences.html` — внутри правой
|
||||
группы топбара, рядом с меню пользователя;
|
||||
- `join.html` — в топбаре, справа от логотипа;
|
||||
- `admin.html` — в шапке сайдбара, рядом с логотипом.
|
||||
|
||||
## Как получена палитра
|
||||
|
||||
### Принцип
|
||||
|
||||
Тёмная схема **не изобретает новых цветов**, а переиспользует уже
|
||||
утверждённые токены комнаты конференции (`tokens.css`, блок
|
||||
`[data-theme='room']`, см. `DESIGN_SYSTEM.md` §1.2) в новых ролях — так
|
||||
оболочка и комната читаются как одна система, когда пользователь входит
|
||||
из тёмного лобби в тёмную комнату. Все переопределения делаются одним
|
||||
блоком `:root { … }` в начале `<style>` каждого файла — он перекрывает
|
||||
одноимённые переменные `:root` из `tokens.css` (та же специфичность,
|
||||
позже в источнике — выигрывает), поэтому весь остальной CSS ниже —
|
||||
дословная копия светлого макета и автоматически перекрашивается.
|
||||
|
||||
### Токены, переиспользованные из комнаты 1:1 (без изменения hex)
|
||||
|
||||
| Токен оболочки (dark) | = токен комнаты | Hex | Новая роль в оболочке |
|
||||
|---|---|---|---|
|
||||
| `--color-bg` | `--color-room-bg` | `#1E1E1E` | фон страницы |
|
||||
| `--color-surface` | `--color-room-surface` | `#2A2A2A` | карточки, топбар, поля |
|
||||
| `--color-border` | `--color-room-tile-border` | `#3F3F3F` | базовая обводка |
|
||||
| `--color-ink-900` | `--color-room-text-primary` | `#E8E8E8` | основной текст |
|
||||
| `--color-ink-500` | `--color-room-text-secondary` | `#B0B0B0` | вторичный текст |
|
||||
| `--color-ink-300` | `--color-room-text-tertiary` | `#7A7A7A` | плейсхолдеры/disabled |
|
||||
| `--color-ink-700`, `--color-accent`, `--color-border-pill`, `--color-success`, `--color-success-dot` | `--color-room-mic-on` | `#7FDDA8` | мята: бренд/заголовки/ссылки/CTA/«успех» — единый пастельно-зелёный вектор (см. ниже) |
|
||||
| `--color-danger`, `--color-danger-dot` | `--color-room-mic-off` | `#EB93A1` | розово-красный: текст/иконки/обводка статуса «отменена»/ошибки |
|
||||
| `--color-warning`, `--color-warning-dot` | `--color-room-speaker-ring` | `#D6A83D` | янтарь: статус «запланирована» |
|
||||
| `--color-focus-ring` | `--color-room-focus-ring` | `#D6A83D` | кольцо `:focus-visible` — то же значение, что и в комнате |
|
||||
| `--color-danger-solid` | `--color-room-danger-bg` | `#B85468` | заливка **сплошных** danger-кнопок (см. ниже, почему это отдельный токен) |
|
||||
| `--color-danger-solid-hover` | `--color-room-danger-bg-hover` | `#B9495F` | hover сплошной danger-кнопки |
|
||||
|
||||
**Одна мята — три роли.** В светлой оболочке бренд-индиго (`--color-ink-700`,
|
||||
заголовки/ссылки/обводка pill-кнопок) и мятный CTA (`--color-accent`) — два
|
||||
разных токена с разными hex. В комнате пастельная мята уже одна на всё
|
||||
«позитивное» (`--color-room-mic-on` = «микрофон включён» + активная
|
||||
демонстрация экрана + счётчик участников + кнопка отправки в чате). Чтобы
|
||||
не завозить в тёмную оболочку новый, ничем не подкреплённый в комнате
|
||||
оттенок, `--color-ink-700` и `--color-accent` в тёмной оболочке **сведены
|
||||
к одному и тому же** `#7FDDA8` — бренд, заголовки, ссылки, обводка
|
||||
secondary-кнопок и заливка CTA используют один и тот же «фирменный» зелёный.
|
||||
Различие ролей (бренд-текст vs интерактивная заливка) остаётся читаемым за
|
||||
счёт контекста (текст vs заливка с тёмным текстом поверх), а не за счёт
|
||||
второго оттенка — это соответствует прецеденту самой комнаты.
|
||||
|
||||
**Почему заливка danger-кнопок — отдельный токен, а не `--color-danger`.**
|
||||
В светлой теме один hex (`#C7364F`) одинаково хорошо работает и как текст на
|
||||
белом, и как заливка кнопки с белым текстом (тёмный, насыщенный тон). В
|
||||
тёмной теме эти две роли расходятся: `--color-room-mic-off` (`#EB93A1`) —
|
||||
светлый пастельный тон, читаемый как **текст на тёмном фоне** (6.30:1 на
|
||||
`#2A2A2A`), но слишком светлый, чтобы на нём читался белый текст кнопки
|
||||
(≈1.9:1, провал). Поэтому для сплошных danger-кнопок (`.btn-danger` —
|
||||
«Отменить все повторения» в календаре, «Удалить» в админке) заведена
|
||||
отдельная пара `--color-danger-solid` / `--color-danger-solid-hover`,
|
||||
переиспользующая уже готовые `--color-room-danger-bg(-hover)` (`#B85468` /
|
||||
`#B9495F`, задокументированы в `DESIGN_SYSTEM.md` §1.2 с контрастом
|
||||
4.66:1/5.02:1 для белого текста).
|
||||
|
||||
### Новые токены (введены только там, где у комнаты нет готового аналога)
|
||||
|
||||
Комната — экран без брендинга, форм и многошаговых карточек, поэтому под
|
||||
нужды оболочки понадобилось несколько новых значений. Все получены
|
||||
подмешиванием уже утверждённых цветов комнаты к графитовому фону/друг к
|
||||
другу (не независимо придуманные тона), проверены по WCAG 2.1 (см. расчёт
|
||||
ниже, тот же метод, что в `design/tools/contrast.py`):
|
||||
|
||||
| Токен | Hex | Как получен | Контраст | Роль |
|
||||
|---|---|---|---|---|
|
||||
| `--color-bg-alt` | `#242424` | между `--color-room-bg` (`#1E1E1E`) и `--color-room-surface` (`#2A2A2A`) | — | альт-фон секций, инпуты, disabled-поля |
|
||||
| `--color-border-strong` | `#585858` | осветление `--color-border` | 2.02:1 на surface (нетекст, декоративный hover-акцент — дублируется тенью/курсором, не единственный носитель состояния) | обводка при hover |
|
||||
| `--color-ink-400` | `#5D9A78` | мята `#7FDDA8` × фон `#1E1E1E`, смешение 35% | 5.05:1 на bg / 4.35:1 на surface | эйбрау-подписи `[ В СКОБКАХ ]`, крупные подписи (аналог ⚠︎ large-text-only из светлой темы, но с запасом) |
|
||||
| `--color-accent-hover` | `#75CA9A` | мята × фон, смешение 10% | текст `#10331F` на заливке: 7.04:1 | hover CTA |
|
||||
| `--color-accent-active` | `#6CB78C` | мята × фон, смешение 20% | текст `#10331F` на заливке: 5.80:1 | active/pressed CTA |
|
||||
| `--color-accent-text` | `#10331F` | **не новый** — переиспользован буквальный hex, уже встречающийся инлайн в `room.html` (`.chat-input-row button`, `.tb-btn.is-positive-active`, `.badge-count`) как цвет текста на заливке `--color-room-mic-on` | 8.45:1 на мяте | текст на CTA/бренд-заливках мятой |
|
||||
| `--color-success-bg` | `rgba(127,221,168,.14)` | мята с низкой непрозрачностью поверх тёмной поверхности | — | подложка бейджей «идёт сейчас»/«свободна»/«активен» |
|
||||
| `--color-danger-bg` | `rgba(235,147,161,.14)` | розовый (mic-off) с низкой непрозрачностью | — | подложка бейджей «отменена» |
|
||||
| `--color-warning-bg` | `rgba(214,168,61,.16)` | янтарь (speaker-ring) с низкой непрозрачностью | — | подложка бейджей «запланирована» |
|
||||
| `--shadow-sm/md/lg/glass` | `rgba(0,0,0,.35–.5)` | по образцу `--shadow-room-panel` комнаты (тёмные тени вместо индиго-тонированных) | — | тени карточек/модалок/glass-панелей |
|
||||
|
||||
Контраст всех текстовых пар проверен по формуле WCAG 2.1 — пары, использованные
|
||||
в макетах (основной текст, вторичный текст, мята/роза/янтарь как текст на
|
||||
`bg`/`surface`/`tile`, белый текст на `--color-danger-solid`), все дают
|
||||
≥ 4.5:1 (текст) либо ≥ 3:1 (декоративные элементы, статус которых также
|
||||
задублирован иконкой/текстом — см. правило §5 `DESIGN_SYSTEM.md`). Регрессия
|
||||
этих пар — в `design/tools/contrast.py`.
|
||||
|
||||
## Точечные правки поверх скопированного CSS
|
||||
|
||||
Для каждого файла CSS светлого макета скопирован дословно (все `.class`
|
||||
правила без изменений) — перекраска идёт только через блок `:root`. Ручных
|
||||
точечных правок пять видов, во всех случаях — потому что исходное правило
|
||||
использовало **литеральный hex/rgba**, а не `var()`, и не могло
|
||||
перекраситься автоматически:
|
||||
|
||||
1. **Аватары и `.weekday-chip.is-selected`/`.badge-role-admin`** — в
|
||||
светлой теме заливка `var(--color-ink-700)` (тёмный индиго) сочеталась с
|
||||
жёстким `color: #fff`. Так как `--color-ink-700` теперь = мята (светлый
|
||||
тон), белый текст перестаёт читаться (≈1.9:1) — заменено на
|
||||
`var(--color-accent-text)` (`#10331F`, 8.45:1 на мяте).
|
||||
2. **`.btn-danger`** (`calendar.html`, `admin.html`) — добавлена отдельная
|
||||
заливка `var(--color-danger-solid)` вместо `var(--color-danger)` (см.
|
||||
таблицу выше); белый текст кнопки не менялся.
|
||||
3. **`.copy-toast`** (`my-conferences.html`) — фон был `var(--color-ink-900)`
|
||||
(тёмный в светлой теме → стал бы светло-серым в тёмной, ломая тёмный
|
||||
тултип); заменён на буквальный `#333333` (= `--color-room-surface-raised`),
|
||||
текст остаётся `var(--color-ink-900)` (теперь светлый) — тултип снова
|
||||
тёмный чип со светлым текстом.
|
||||
4. **Декоративные градиенты/rgba-подложки** (`login.html`
|
||||
`.brand-panel`/`.brand-panel::after`, `lobby.html` фон `body`,
|
||||
`calendar.html` `.day-col.is-today`/`.cancel-alert`, `login.html`/
|
||||
`join.html`/`calendar.html` box-shadow фокуса и ошибки, glass-обводка
|
||||
`rgba(255,255,255,.7)` в `login.html`/`admin.html`, disabled-заливка
|
||||
кнопок) — старые rgba были построены на индиго/старом danger-hex;
|
||||
заменены на тональные аналоги новой палитры (мятное свечение вместо
|
||||
индигового/мятного на белом, янтарный focus-glow вместо индигового,
|
||||
розовый error-glow, `rgba(255,255,255,.08)` вместо `.7` для тёмного
|
||||
стекла).
|
||||
5. **`color-scheme: dark`** добавлен в блок `:root`, чтобы нативные
|
||||
элементы форм (скроллбар, чекбоксы без кастомной стилизации) тоже
|
||||
рендерились в тёмной палитре браузера.
|
||||
|
||||
## Доступность (проверено при подборе палитры)
|
||||
|
||||
- Текст ≥ 4.5:1 везде, кроме крупных/декоративных элементов (аналогично
|
||||
правилу §5 `DESIGN_SYSTEM.md` для светлой темы, порог 3:1).
|
||||
- Статус не передаётся только цветом: бейджи конференций — иконка + текст
|
||||
(как в светлой теме), «скопировано» — смена иконки `Copy → Check` +
|
||||
текстовый тултип, ошибки полей — иконка `AlertCircle` + текст под полем,
|
||||
`:focus-visible` — везде видимое кольцо (`--color-focus-ring`, тот же
|
||||
янтарь, что и в комнате).
|
||||
- Палитра пастельная (мята/роза/янтарь той же светлоты, что уже утверждена
|
||||
для комнаты), неоновых/кислотных акцентов не вводилось.
|
||||
303
design/mockups/dark/admin.html
Normal file
303
design/mockups/dark/admin.html
Normal file
@@ -0,0 +1,303 @@
|
||||
<!doctype html>
|
||||
<html lang="ru" data-theme="shell-dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>VidConf — Админка — тёмная тема, НА СОГЛАСОВАНИИ</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../tokens.css">
|
||||
<style>
|
||||
/* =====================================================================
|
||||
* ТЁМНЫЙ ВАРИАНТ ОБОЛОЧКИ — ПРЕДЛОЖЕНИЕ, НЕ УТВЕРЖДЕНО.
|
||||
* Разметка и контент идентичны design/mockups/admin.html 1:1, меняется
|
||||
* только цветовая схема — палитра переиспользует токены комнаты
|
||||
* конференции. Обоснование цветов — design/mockups/dark/README.md.
|
||||
* ===================================================================== */
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
|
||||
--color-bg: #1E1E1E;
|
||||
--color-bg-alt: #242424;
|
||||
--color-surface: #2A2A2A;
|
||||
--color-surface-glass: rgba(51, 51, 51, .55);
|
||||
|
||||
--color-border: #3F3F3F;
|
||||
--color-border-strong: #585858;
|
||||
--color-border-pill: #7FDDA8;
|
||||
|
||||
--color-ink-900: #E8E8E8;
|
||||
--color-ink-700: #7FDDA8;
|
||||
--color-ink-500: #B0B0B0;
|
||||
--color-ink-400: #5D9A78;
|
||||
--color-ink-300: #7A7A7A;
|
||||
|
||||
--color-accent: #7FDDA8;
|
||||
--color-accent-hover: #75CA9A;
|
||||
--color-accent-active: #6CB78C;
|
||||
--color-accent-text: #10331F;
|
||||
|
||||
--color-success: #7FDDA8;
|
||||
--color-success-bg: rgba(127, 221, 168, .14);
|
||||
--color-success-dot: #7FDDA8;
|
||||
|
||||
--color-danger: #EB93A1;
|
||||
--color-danger-bg: rgba(235, 147, 161, .14);
|
||||
--color-danger-dot: #EB93A1;
|
||||
--color-danger-solid: #B85468;
|
||||
--color-danger-solid-hover: #B9495F;
|
||||
|
||||
--color-warning: #D6A83D;
|
||||
--color-warning-bg: rgba(214, 168, 61, .16);
|
||||
--color-warning-dot: #D6A83D;
|
||||
|
||||
--color-focus-ring: #D6A83D;
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, .35);
|
||||
--shadow-md: 0 4px 16px rgba(0, 0, 0, .4);
|
||||
--shadow-lg: 0 12px 32px rgba(0, 0, 0, .5);
|
||||
--shadow-glass: 0 8px 24px rgba(0, 0, 0, .45);
|
||||
}
|
||||
|
||||
.theme-toggle { display: inline-flex; gap: 2px; background: var(--color-bg-alt); padding: 4px; border-radius: var(--radius-full); border: 1px solid var(--color-border); }
|
||||
.theme-toggle-btn { width: 32px; height: 32px; border-radius: 50%; border: none; background: transparent; color: var(--color-ink-500); display: flex; align-items: center; justify-content: center; }
|
||||
.theme-toggle-btn .icon { width: 16px; height: 16px; }
|
||||
.theme-toggle-btn:hover { color: var(--color-ink-900); }
|
||||
.theme-toggle-btn.is-active { background: var(--color-surface); color: var(--color-ink-700); box-shadow: var(--shadow-sm); }
|
||||
|
||||
html { min-width: 1280px; }
|
||||
|
||||
.app-shell { display: flex; min-height: 100vh; }
|
||||
|
||||
/* ---------- Сайдбар ---------- */
|
||||
.sidebar { width: 260px; flex-shrink: 0; background: var(--color-surface); border-right: 1px solid var(--color-border); padding: var(--space-6) var(--space-4); display: flex; flex-direction: column; }
|
||||
.sidebar-head { display: flex; align-items: center; justify-content: space-between; padding: 0 var(--space-2); margin-bottom: var(--space-8); }
|
||||
.brand-mark { display: inline-flex; align-items: center; gap: 8px; font: var(--text-h3); color: var(--color-ink-700); }
|
||||
.brand-mark .logo-mark { width: 22px; height: 22px; flex-shrink: 0; }
|
||||
|
||||
.side-nav { display: flex; flex-direction: column; gap: 2px; }
|
||||
.side-link {
|
||||
display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-radius: var(--radius-md);
|
||||
font: var(--text-body); font-weight: 600; color: var(--color-ink-500); text-decoration: none;
|
||||
}
|
||||
.side-link:hover { background: var(--color-bg-alt); color: var(--color-ink-900); }
|
||||
.side-link.is-active { background: var(--color-bg-alt); color: var(--color-ink-700); }
|
||||
.side-link .icon { color: inherit; }
|
||||
|
||||
.side-footer { margin-top: auto; padding: var(--space-3) var(--space-2); border-top: 1px solid var(--color-border); }
|
||||
.avatar-btn { display: flex; align-items: center; gap: 8px; padding: 8px 0; width: 100%; background: none; border: none; text-align: left; }
|
||||
.avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--color-ink-700); color: var(--color-accent-text); display:flex; align-items:center; justify-content:center; font: var(--text-caption); text-transform: none; letter-spacing: 0; flex-shrink: 0; }
|
||||
.avatar-btn div strong { display: block; font: var(--text-body); color: var(--color-ink-900); }
|
||||
.avatar-btn div span { font: 12px var(--font-body); color: var(--color-ink-500); }
|
||||
|
||||
/* ---------- Контент ---------- */
|
||||
main { flex: 1; padding: var(--space-8); max-width: 1220px; }
|
||||
.page-head { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: var(--space-6); }
|
||||
.page-head h1 { font: var(--text-display-lg); font-family: var(--font-display); color: var(--color-ink-700); margin: 0 0 6px; }
|
||||
.page-head p { font: var(--text-body-lg); color: var(--color-ink-500); margin: 0; }
|
||||
|
||||
.btn { display: inline-flex; align-items: center; gap: 8px; border: none; cursor: pointer; font-family: var(--font-body); white-space: nowrap; transition: background var(--transition-fast), box-shadow var(--transition-fast); }
|
||||
.btn-primary { background: var(--color-accent); color: var(--color-accent-text); font: var(--text-body); font-weight: 700; padding: 11px 22px; border-radius: var(--radius-full); }
|
||||
.btn-primary:hover { background: var(--color-accent-hover); box-shadow: var(--shadow-md); }
|
||||
.btn-secondary { background: transparent; color: var(--color-ink-700); border: 1.5px solid var(--color-border-pill); font: var(--text-body); font-weight: 600; padding: 9px 18px; border-radius: var(--radius-full); }
|
||||
.btn-secondary:hover { background: var(--color-bg-alt); }
|
||||
.btn-danger { background: var(--color-danger); color: #fff; font: var(--text-body); font-weight: 700; padding: 9px 18px; border-radius: var(--radius-full); }
|
||||
|
||||
/* Стат-виджеты (glass) */
|
||||
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-4); margin-bottom: var(--space-8); }
|
||||
.stat-glass {
|
||||
background: var(--color-surface-glass); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255,255,255,.08); box-shadow: var(--shadow-glass); border-radius: var(--radius-xl);
|
||||
padding: var(--space-5); background-color: var(--color-bg-alt);
|
||||
}
|
||||
.stat-glass strong { display: block; font: var(--text-display-xl); font-family: var(--font-display); font-size: 36px; color: var(--color-ink-700); }
|
||||
.stat-glass span { font: var(--text-body); color: var(--color-ink-500); }
|
||||
|
||||
/* Табы разделов */
|
||||
.section-tabs { display: flex; gap: var(--space-2); margin-bottom: var(--space-5); border-bottom: 1px solid var(--color-border); }
|
||||
.section-tab { padding: 10px 4px; margin-right: var(--space-6); background: none; border: none; border-bottom: 2px solid transparent; font: var(--text-body-lg); font-weight: 600; color: var(--color-ink-500); }
|
||||
.section-tab.is-active { color: var(--color-ink-700); border-color: var(--color-accent-active); }
|
||||
|
||||
.toolbar-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-5); gap: var(--space-4); }
|
||||
.search-wrap { position: relative; width: 320px; }
|
||||
.search-wrap .icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--color-ink-400); }
|
||||
.search-wrap input { width: 100%; font: var(--text-body); padding: 9px 12px 9px 38px; border-radius: var(--radius-full); border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-ink-900); }
|
||||
.search-wrap input::placeholder { color: var(--color-ink-300); }
|
||||
.search-wrap input:focus { outline: none; border-color: var(--color-focus-ring); }
|
||||
|
||||
/* Таблица */
|
||||
.table-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); overflow: hidden; margin-bottom: var(--space-8); }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
thead th { text-align: left; background: var(--color-bg-alt); font: var(--text-caption); color: var(--color-ink-500); padding: 12px 20px; text-transform: uppercase; }
|
||||
tbody td { padding: 14px 20px; font: var(--text-body-lg); color: var(--color-ink-900); border-top: 1px solid var(--color-border); }
|
||||
tbody tr:hover { background: var(--color-bg-alt); }
|
||||
|
||||
.badge { display: inline-flex; align-items: center; gap: 6px; padding: 4px 12px; border-radius: var(--radius-full); font: var(--text-caption); white-space: nowrap; }
|
||||
.badge-free { background: var(--color-success-bg); color: var(--color-success); }
|
||||
.badge-busy { background: var(--color-danger-bg); color: var(--color-danger); }
|
||||
.badge-booked { background: var(--color-warning-bg); color: var(--color-warning); }
|
||||
.badge-locked { background: var(--color-bg-alt); color: var(--color-ink-700); }
|
||||
.badge-role-admin { background: var(--color-ink-700); color: var(--color-accent-text); }
|
||||
.badge-role-user { background: var(--color-bg-alt); color: var(--color-ink-500); }
|
||||
|
||||
.row-actions { display: flex; gap: 6px; }
|
||||
.icon-btn { width: 32px; height: 32px; border-radius: var(--radius-md); border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-ink-500); display:flex; align-items:center; justify-content:center; }
|
||||
.icon-btn:hover { border-color: var(--color-border-strong); color: var(--color-ink-900); }
|
||||
.icon-btn.danger:hover { border-color: var(--color-danger); color: var(--color-danger); background: var(--color-danger-bg); }
|
||||
|
||||
.user-cell { display: flex; align-items: center; gap: 10px; }
|
||||
.user-cell .avatar { width: 32px; height: 32px; font-size: 12px; }
|
||||
.user-cell div span { display: block; font: 13px var(--font-body); color: var(--color-ink-500); }
|
||||
|
||||
/* Секция рассылки */
|
||||
.invite-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); padding: var(--space-6); display: grid; grid-template-columns: 1fr 320px; gap: var(--space-8); }
|
||||
.invite-card h2 { font: var(--text-h2); margin: 0 0 4px; color: var(--color-ink-900); }
|
||||
.invite-card p.desc { font: var(--text-body); color: var(--color-ink-500); margin: 0 0 var(--space-5); }
|
||||
.invite-card textarea {
|
||||
width: 100%; min-height: 140px; font: var(--text-body-lg); color: var(--color-ink-900); font-family: var(--font-mono);
|
||||
padding: 14px; border-radius: var(--radius-md); border: 1px solid var(--color-border); background: var(--color-surface); resize: vertical;
|
||||
}
|
||||
.invite-card textarea:focus { outline: none; border-color: var(--color-focus-ring); }
|
||||
.invite-side { background: var(--color-bg-alt); border-radius: var(--radius-xl); padding: var(--space-5); }
|
||||
.invite-side h3 { font: var(--text-h3); margin: 0 0 var(--space-3); color: var(--color-ink-900); }
|
||||
.invite-side ul { margin: 0 0 var(--space-5); padding-left: 18px; font: var(--text-body); color: var(--color-ink-500); }
|
||||
.invite-side li { margin-bottom: 6px; }
|
||||
|
||||
/* ---------- Патчи тёмной темы: заливка mint требует тёмного текста, а не белого ---------- */
|
||||
.btn-danger { background: var(--color-danger-solid); }
|
||||
.btn-danger:hover { filter: none; background: var(--color-danger-solid-hover); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<svg style="display:none">
|
||||
<symbol id="i-dashboard" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></symbol>
|
||||
<symbol id="i-video" viewBox="0 0 24 24"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="m22 8-6 4 6 4V8Z"/></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||||
<symbol id="i-users" viewBox="0 0 24 24"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></symbol>
|
||||
<symbol id="i-mail" viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m2 6 10 7 10-7"/></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></symbol>
|
||||
<symbol id="i-plus" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></symbol>
|
||||
<symbol id="i-pencil" viewBox="0 0 24 24"><path d="M17 3a2.83 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></symbol>
|
||||
<symbol id="i-trash" viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></symbol>
|
||||
<symbol id="i-send" viewBox="0 0 24 24"><path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/></symbol>
|
||||
<symbol id="i-check-circle" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></symbol>
|
||||
<symbol id="i-ban" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="4.9" y1="4.9" x2="19.1" y2="19.1"/></symbol>
|
||||
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></symbol>
|
||||
<symbol id="i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></symbol>
|
||||
<symbol id="i-moon" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z"/></symbol>
|
||||
</svg>
|
||||
|
||||
<div class="app-shell">
|
||||
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-head">
|
||||
<div class="brand-mark"><svg class="logo-mark" viewBox="0 0 32 32" fill="none" aria-hidden="true"><polygon points="21,16 18.5,20.3 13.5,20.3 11,16 13.5,11.7 18.5,11.7" fill="currentColor"/><circle cx="16" cy="16" r="13" stroke="currentColor" stroke-width="2.6" stroke-dasharray="12 6"/></svg> VidConf</div>
|
||||
<div class="theme-toggle" role="group" aria-label="Переключить тему оформления">
|
||||
<button class="theme-toggle-btn" type="button" aria-pressed="false" aria-label="Светлая тема"><svg class="icon"><use href="#i-sun"/></svg></button>
|
||||
<button class="theme-toggle-btn is-active" type="button" aria-pressed="true" aria-label="Тёмная тема"><svg class="icon"><use href="#i-moon"/></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="side-nav">
|
||||
<a class="side-link is-active" href="#rooms"><svg class="icon" style="width:18px;height:18px"><use href="#i-video"/></svg>Конференции</a>
|
||||
<a class="side-link" href="#closed"><svg class="icon" style="width:18px;height:18px"><use href="#i-lock"/></svg>Закрытые конференции</a>
|
||||
<a class="side-link" href="#users"><svg class="icon" style="width:18px;height:18px"><use href="#i-users"/></svg>Пользователи</a>
|
||||
<a class="side-link" href="#invites"><svg class="icon" style="width:18px;height:18px"><use href="#i-mail"/></svg>Рассылка приглашений</a>
|
||||
</nav>
|
||||
<div class="side-footer">
|
||||
<button class="avatar-btn"><span class="avatar">АИ</span><div><strong>Анна Иванова</strong><span>Администратор</span></div></button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main>
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Администрирование</h1>
|
||||
<p>Управление конференциями, доступом и пользователями VidConf</p>
|
||||
</div>
|
||||
<button class="btn btn-primary"><svg class="icon" style="width:16px;height:16px"><use href="#i-plus"/></svg>Добавить конференцию</button>
|
||||
</div>
|
||||
|
||||
<section class="stat-grid">
|
||||
<div class="stat-glass"><strong>100</strong><span>Всего конференций</span></div>
|
||||
<div class="stat-glass"><strong>12</strong><span>Активны сейчас</span></div>
|
||||
<div class="stat-glass"><strong>34</strong><span>Забронировано сегодня</span></div>
|
||||
<div class="stat-glass"><strong>248</strong><span>Пользователей</span></div>
|
||||
</section>
|
||||
|
||||
<div class="section-tabs">
|
||||
<button class="section-tab is-active">Конференции</button>
|
||||
<button class="section-tab">Закрытые конференции</button>
|
||||
<button class="section-tab">Пользователи</button>
|
||||
<button class="section-tab">Рассылка приглашений</button>
|
||||
</div>
|
||||
|
||||
<!-- ===================== КОНФЕРЕНЦИИ ===================== -->
|
||||
<div class="toolbar-row">
|
||||
<div class="search-wrap"><svg class="icon"><use href="#i-search"/></svg><input type="text" placeholder="Поиск конференции…"></div>
|
||||
<span style="font:14px var(--font-body); color:var(--color-ink-500)">Показано 6 из 100</span>
|
||||
</div>
|
||||
|
||||
<div class="table-card">
|
||||
<table>
|
||||
<thead><tr><th>Конференция</th><th>Вместимость</th><th>Статус</th><th>Тип</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Переговорная №01</td><td>8 мест</td><td><span class="badge badge-free"><svg class="icon" style="width:12px;height:12px"><use href="#i-check-circle"/></svg>Свободна</span></td><td>Открытая</td>
|
||||
<td><div class="row-actions"><button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:16px;height:16px"><use href="#i-pencil"/></svg></button><button class="icon-btn danger" aria-label="Удалить"><svg class="icon" style="width:16px;height:16px"><use href="#i-trash"/></svg></button></div></td></tr>
|
||||
<tr><td>Переговорная №02</td><td>10 мест</td><td><span class="badge badge-busy"><svg class="icon" style="width:12px;height:12px"><use href="#i-ban"/></svg>Занята</span></td><td>Открытая</td>
|
||||
<td><div class="row-actions"><button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:16px;height:16px"><use href="#i-pencil"/></svg></button><button class="icon-btn danger" aria-label="Удалить"><svg class="icon" style="width:16px;height:16px"><use href="#i-trash"/></svg></button></div></td></tr>
|
||||
<tr><td>Переговорная №04</td><td>12 мест</td><td><span class="badge badge-locked"><svg class="icon" style="width:12px;height:12px"><use href="#i-lock"/></svg>По паролю</span></td><td>Закрытая</td>
|
||||
<td><div class="row-actions"><button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:16px;height:16px"><use href="#i-pencil"/></svg></button><button class="icon-btn danger" aria-label="Удалить"><svg class="icon" style="width:16px;height:16px"><use href="#i-trash"/></svg></button></div></td></tr>
|
||||
<tr><td>Переговорная №42</td><td>16 мест</td><td><span class="badge badge-booked"><svg class="icon" style="width:12px;height:12px"><use href="#i-clock"/></svg>Забронирована</span></td><td>Открытая</td>
|
||||
<td><div class="row-actions"><button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:16px;height:16px"><use href="#i-pencil"/></svg></button><button class="icon-btn danger" aria-label="Удалить"><svg class="icon" style="width:16px;height:16px"><use href="#i-trash"/></svg></button></div></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ===================== ПОЛЬЗОВАТЕЛИ ===================== -->
|
||||
<div class="page-head" style="margin-bottom: var(--space-4)"><h2 style="font:var(--text-h2); color:var(--color-ink-900); margin:0">Пользователи</h2></div>
|
||||
<div class="table-card">
|
||||
<table>
|
||||
<thead><tr><th>Пользователь</th><th>Роль</th><th>Регистрация</th><th>Статус</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><div class="user-cell"><span class="avatar">АИ</span><div>Анна Иванова<span>a.ivanova@vidconf.dev</span></div></div></td>
|
||||
<td><span class="badge badge-role-admin">Администратор</span></td>
|
||||
<td>03.02.2026</td>
|
||||
<td><span class="badge badge-free"><svg class="icon" style="width:12px;height:12px"><use href="#i-check-circle"/></svg>Активен</span></td>
|
||||
<td><div class="row-actions"><button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:16px;height:16px"><use href="#i-pencil"/></svg></button></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div class="user-cell"><span class="avatar">МК</span><div>Мария Кузнецова<span>m.kuznetsova@company.ru</span></div></div></td>
|
||||
<td><span class="badge badge-role-user">Пользователь</span></td>
|
||||
<td>15.07.2026</td>
|
||||
<td><span class="badge badge-booked"><svg class="icon" style="width:12px;height:12px"><use href="#i-clock"/></svg>Ждёт подтверждения</span></td>
|
||||
<td><div class="row-actions"><button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:16px;height:16px"><use href="#i-pencil"/></svg></button><button class="icon-btn danger" aria-label="Удалить"><svg class="icon" style="width:16px;height:16px"><use href="#i-trash"/></svg></button></div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ===================== РАССЫЛКА ПРИГЛАШЕНИЙ ===================== -->
|
||||
<div class="page-head" style="margin-bottom: var(--space-4)"><h2 style="font:var(--text-h2); color:var(--color-ink-900); margin:0">Рассылка приглашений</h2></div>
|
||||
<div class="invite-card">
|
||||
<div>
|
||||
<h2>Пригласить новых пользователей</h2>
|
||||
<p class="desc">Каждый адрес получит письмо со ссылкой регистрации, действительной 7 дней.</p>
|
||||
<textarea placeholder="i.sokolov@company.ru o.panina@company.ru v.sedykh@company.ru">i.sokolov@company.ru
|
||||
o.panina@company.ru
|
||||
v.sedykh@company.ru</textarea>
|
||||
<button class="btn btn-primary" style="margin-top: var(--space-4)"><svg class="icon" style="width:16px;height:16px"><use href="#i-send"/></svg>Отправить приглашения (3)</button>
|
||||
</div>
|
||||
<div class="invite-side">
|
||||
<h3>Последняя рассылка</h3>
|
||||
<ul>
|
||||
<li>28.06.2026 · 12 приглашений</li>
|
||||
<li>9 приняли, 3 ожидают</li>
|
||||
</ul>
|
||||
<button class="btn btn-secondary" style="width:100%; justify-content:center">Посмотреть журнал</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
558
design/mockups/dark/calendar.html
Normal file
558
design/mockups/dark/calendar.html
Normal file
@@ -0,0 +1,558 @@
|
||||
<!doctype html>
|
||||
<html lang="ru" data-theme="shell-dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>VidConf — Календарь конференций — тёмная тема, НА СОГЛАСОВАНИИ</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../tokens.css">
|
||||
<style>
|
||||
/* =====================================================================
|
||||
* ТЁМНЫЙ ВАРИАНТ ОБОЛОЧКИ — ПРЕДЛОЖЕНИЕ, НЕ УТВЕРЖДЕНО.
|
||||
* Разметка и контент идентичны design/mockups/calendar.html 1:1, меняется
|
||||
* только цветовая схема — палитра переиспользует токены комнаты
|
||||
* конференции. Обоснование цветов — design/mockups/dark/README.md.
|
||||
* ===================================================================== */
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
|
||||
--color-bg: #1E1E1E;
|
||||
--color-bg-alt: #242424;
|
||||
--color-surface: #2A2A2A;
|
||||
--color-surface-glass: rgba(51, 51, 51, .55);
|
||||
|
||||
--color-border: #3F3F3F;
|
||||
--color-border-strong: #585858;
|
||||
--color-border-pill: #7FDDA8;
|
||||
|
||||
--color-ink-900: #E8E8E8;
|
||||
--color-ink-700: #7FDDA8;
|
||||
--color-ink-500: #B0B0B0;
|
||||
--color-ink-400: #5D9A78;
|
||||
--color-ink-300: #7A7A7A;
|
||||
|
||||
--color-accent: #7FDDA8;
|
||||
--color-accent-hover: #75CA9A;
|
||||
--color-accent-active: #6CB78C;
|
||||
--color-accent-text: #10331F;
|
||||
|
||||
--color-success: #7FDDA8;
|
||||
--color-success-bg: rgba(127, 221, 168, .14);
|
||||
--color-success-dot: #7FDDA8;
|
||||
|
||||
--color-danger: #EB93A1;
|
||||
--color-danger-bg: rgba(235, 147, 161, .14);
|
||||
--color-danger-dot: #EB93A1;
|
||||
--color-danger-solid: #B85468;
|
||||
--color-danger-solid-hover: #B9495F;
|
||||
|
||||
--color-warning: #D6A83D;
|
||||
--color-warning-bg: rgba(214, 168, 61, .16);
|
||||
--color-warning-dot: #D6A83D;
|
||||
|
||||
--color-focus-ring: #D6A83D;
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, .35);
|
||||
--shadow-md: 0 4px 16px rgba(0, 0, 0, .4);
|
||||
--shadow-lg: 0 12px 32px rgba(0, 0, 0, .5);
|
||||
--shadow-glass: 0 8px 24px rgba(0, 0, 0, .45);
|
||||
}
|
||||
|
||||
.theme-toggle { display: inline-flex; gap: 2px; background: var(--color-bg-alt); padding: 4px; border-radius: var(--radius-full); border: 1px solid var(--color-border); }
|
||||
.theme-toggle-btn { width: 32px; height: 32px; border-radius: 50%; border: none; background: transparent; color: var(--color-ink-500); display: flex; align-items: center; justify-content: center; }
|
||||
.theme-toggle-btn .icon { width: 16px; height: 16px; }
|
||||
.theme-toggle-btn:hover { color: var(--color-ink-900); }
|
||||
.theme-toggle-btn.is-active { background: var(--color-surface); color: var(--color-ink-700); box-shadow: var(--shadow-sm); }
|
||||
|
||||
html { min-width: 1280px; }
|
||||
|
||||
.topbar {
|
||||
position: sticky; top: 0; z-index: 20;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: var(--space-4) var(--space-8);
|
||||
background: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.brand-mark { display: inline-flex; align-items: center; gap: 8px; font: var(--text-h3); color: var(--color-ink-700); }
|
||||
.brand-mark .logo-mark { width: 22px; height: 22px; flex-shrink: 0; }
|
||||
.topbar-right { display: flex; align-items: center; gap: var(--space-5); }
|
||||
.nav-link { font: var(--text-body); font-weight: 600; color: var(--color-ink-500); text-decoration: none; }
|
||||
.nav-link.is-active { color: var(--color-ink-700); }
|
||||
.avatar-btn { display: flex; align-items: center; gap: 8px; background: var(--color-bg-alt); border: 1px solid var(--color-border); border-radius: var(--radius-full); padding: 6px 14px 6px 6px; }
|
||||
.avatar { width: 28px; height: 28px; border-radius: 50%; background: var(--color-ink-700); color: var(--color-accent-text); display:flex; align-items:center; justify-content:center; font: var(--text-caption); text-transform: none; letter-spacing: 0; }
|
||||
.avatar-btn .user-name { font: var(--text-body); font-weight: 600; color: var(--color-ink-900); }
|
||||
|
||||
main { padding: var(--space-8); max-width: 1440px; margin: 0 auto; }
|
||||
|
||||
.page-head { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: var(--space-6); }
|
||||
.page-head h1 { font: var(--text-display-lg); font-family: var(--font-display); color: var(--color-ink-700); margin: 0 0 6px; }
|
||||
.page-head p { font: var(--text-body-lg); color: var(--color-ink-500); margin: 0; max-width: 560px; }
|
||||
|
||||
.btn { display: inline-flex; align-items: center; gap: 8px; border: none; cursor: pointer; font-family: var(--font-body); white-space: nowrap; transition: background var(--transition-fast), box-shadow var(--transition-fast); }
|
||||
.btn-primary { background: var(--color-accent); color: var(--color-accent-text); font: var(--text-body); font-weight: 700; padding: 11px 22px; border-radius: var(--radius-full); }
|
||||
.btn-primary:hover { background: var(--color-accent-hover); box-shadow: var(--shadow-md); }
|
||||
.btn-primary:active { background: var(--color-accent-active); }
|
||||
.btn-primary:disabled { background: rgba(122,122,122,.3); color: var(--color-ink-300); cursor: not-allowed; }
|
||||
.btn-secondary { background: transparent; color: var(--color-ink-700); border: 1.5px solid var(--color-border-pill); font: var(--text-body); font-weight: 600; padding: 9px 18px; border-radius: var(--radius-full); }
|
||||
.btn-secondary:hover { background: var(--color-bg-alt); }
|
||||
.btn-danger { background: var(--color-danger); color: #fff; font: var(--text-body); font-weight: 700; padding: 9px 18px; border-radius: var(--radius-full); }
|
||||
.btn-danger:hover { filter: brightness(0.92); }
|
||||
.btn-text { background: none; border: none; color: var(--color-danger); font: var(--text-body); font-weight: 600; padding: 4px; }
|
||||
.btn-text:hover { text-decoration: underline; }
|
||||
.btn.full { width: 100%; justify-content: center; }
|
||||
:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
|
||||
|
||||
/* ---------- Основной грид: неделя + карточка создания ---------- */
|
||||
.layout-grid { display: grid; grid-template-columns: 1fr 400px; gap: var(--space-6); align-items: start; }
|
||||
|
||||
.week-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); padding: var(--space-6); }
|
||||
.week-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-5); }
|
||||
.date-nav { display: flex; align-items: center; gap: var(--space-3); }
|
||||
.date-nav button { width: 34px; height: 34px; border-radius: 50%; border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-ink-500); display:flex; align-items:center; justify-content:center; }
|
||||
.date-nav button:hover { border-color: var(--color-border-strong); color: var(--color-ink-900); }
|
||||
.date-nav span { font: var(--text-h3); color: var(--color-ink-900); min-width: 200px; text-align: center; }
|
||||
.view-toggle { display: flex; gap: 4px; background: var(--color-bg-alt); padding: 4px; border-radius: var(--radius-full); }
|
||||
.view-toggle button { padding: 7px 16px; border-radius: var(--radius-full); border: none; background: transparent; font: var(--text-body); font-weight: 600; color: var(--color-ink-500); }
|
||||
.view-toggle button.is-active { background: var(--color-surface); color: var(--color-ink-700); box-shadow: var(--shadow-sm); }
|
||||
|
||||
.week-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--space-4); }
|
||||
.day-col { background: var(--color-bg-alt); border-radius: var(--radius-lg); padding: var(--space-3); display: flex; flex-direction: column; gap: var(--space-2); min-height: 460px; }
|
||||
.day-col.is-today { background: rgba(127,221,168,.10); }
|
||||
.day-col-head { text-align: center; padding-bottom: var(--space-2); margin-bottom: var(--space-1); border-bottom: 1px solid var(--color-border); }
|
||||
.day-col-head .dow { display: block; font: var(--text-caption); color: var(--color-ink-500); }
|
||||
.day-col-head .dom { display: block; font: var(--text-h3); color: var(--color-ink-900); margin-top: 2px; }
|
||||
.day-col.is-today .day-col-head .dom { color: var(--color-ink-700); }
|
||||
|
||||
.conf-chip { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-3); display: flex; flex-direction: column; gap: 4px; cursor: pointer; transition: box-shadow var(--transition-fast), border-color var(--transition-fast); text-align: left; }
|
||||
.conf-chip:hover { box-shadow: var(--shadow-sm); border-color: var(--color-border-strong); }
|
||||
.chip-time { display: flex; align-items: center; gap: 4px; font: var(--text-caption); color: var(--color-ink-500); text-transform: none; letter-spacing: normal; }
|
||||
.chip-title { display: flex; align-items: center; gap: 6px; font: var(--text-body); font-weight: 700; color: var(--color-ink-900); }
|
||||
.chip-meta { display: flex; align-items: center; gap: 10px; font: 12px var(--font-body); color: var(--color-ink-500); }
|
||||
.chip-meta .meta-item { display: flex; align-items: center; gap: 4px; }
|
||||
|
||||
.conf-badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px; border-radius: var(--radius-full); font: var(--text-caption); font-size: 11px; width: fit-content; }
|
||||
.conf-badge.scheduled { background: var(--color-warning-bg); color: var(--color-warning); }
|
||||
.conf-badge.pinned { background: var(--color-bg-alt); color: var(--color-ink-700); border: 1px solid var(--color-border-strong); }
|
||||
.conf-badge.live { background: var(--color-success-bg); color: var(--color-success); }
|
||||
.conf-badge.cancelled { background: var(--color-danger-bg); color: var(--color-danger); }
|
||||
|
||||
.conf-chip.is-cancelled { opacity: .6; }
|
||||
.conf-chip.is-cancelled .chip-title { text-decoration: line-through; color: var(--color-ink-500); }
|
||||
|
||||
.day-empty { flex: 1; display: flex; align-items: center; justify-content: center; text-align: center; font: var(--text-body); color: var(--color-ink-300); padding: var(--space-4) 0; }
|
||||
|
||||
/* ---------- Карточка создания / редактирования ---------- */
|
||||
.create-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); padding: var(--space-6); position: sticky; top: 96px; }
|
||||
.create-card h2 { font: var(--text-h2); color: var(--color-ink-900); margin: 0 0 var(--space-5); display: flex; align-items: center; gap: 8px; }
|
||||
|
||||
.field { margin-bottom: var(--space-5); }
|
||||
.field label { display: flex; align-items: center; gap: 6px; font: var(--text-body); font-weight: 600; color: var(--color-ink-900); margin-bottom: var(--space-2); }
|
||||
.field input, .field textarea, .field select {
|
||||
width: 100%; font: var(--text-body-lg); color: var(--color-ink-900);
|
||||
padding: 10px 14px; border-radius: var(--radius-md); border: 1px solid var(--color-border);
|
||||
background: var(--color-surface);
|
||||
}
|
||||
.field input:focus, .field textarea:focus, .field select:focus { outline: none; border-color: var(--color-focus-ring); box-shadow: 0 0 0 3px rgba(214,168,61,.18); }
|
||||
.field-row { display: flex; gap: var(--space-3); }
|
||||
.field-row .field { flex: 1; }
|
||||
.field-hint { font: 12px var(--font-body); color: var(--color-ink-500); margin: 6px 2px 0; }
|
||||
|
||||
/* Тумблер (используется и для «закрытая по паролю», и для «закрепить») */
|
||||
.toggle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-3) 0; border-top: 1px solid var(--color-border); }
|
||||
.toggle-copy strong { display: block; font: var(--text-body); color: var(--color-ink-900); }
|
||||
.toggle-copy span { font: 12px var(--font-body); color: var(--color-ink-500); }
|
||||
.switch { position: relative; width: 44px; height: 24px; flex-shrink: 0; }
|
||||
.switch input { opacity: 0; width: 0; height: 0; }
|
||||
.slider { position: absolute; inset: 0; background: var(--color-border-strong); border-radius: var(--radius-full); transition: background var(--transition-fast); cursor: pointer; }
|
||||
.slider::before { content:""; position:absolute; width:18px; height:18px; left:3px; top:3px; background:#fff; border-radius:50%; transition: transform var(--transition-fast); }
|
||||
.switch input:checked + .slider { background: var(--color-accent-active); }
|
||||
.switch input:checked + .slider::before { transform: translateX(20px); }
|
||||
.switch input:focus-visible + .slider { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
|
||||
|
||||
.password-block { display: none; margin-top: var(--space-4); }
|
||||
.toggle-block:has(#pw-toggle:checked) .password-block { display: block; }
|
||||
|
||||
/* ---------- Блок повторений (recurrence) ---------- */
|
||||
.pin-block .recurrence-panel { display: none; }
|
||||
.pin-block:has(input[type="checkbox"]:checked) .recurrence-panel {
|
||||
display: flex; flex-direction: column; gap: var(--space-2);
|
||||
margin-top: var(--space-4); padding-top: var(--space-4);
|
||||
border-top: 1px dashed var(--color-border);
|
||||
}
|
||||
|
||||
.recurrence-option-row {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 10px 12px; border: 1px solid var(--color-border); border-radius: var(--radius-md);
|
||||
cursor: pointer; font: var(--text-body); font-weight: 600; color: var(--color-ink-900);
|
||||
transition: border-color var(--transition-fast), background var(--transition-fast);
|
||||
}
|
||||
.recurrence-option-row input[type="radio"] { width: 16px; height: 16px; accent-color: var(--color-ink-700); flex-shrink: 0; }
|
||||
.recurrence-option:has(input:checked) .recurrence-option-row { border-color: var(--color-ink-700); background: var(--color-bg-alt); }
|
||||
|
||||
.recurrence-sub { display: none; padding: var(--space-3) 12px 4px 38px; }
|
||||
.recurrence-option:has(input:checked) .recurrence-sub { display: block; }
|
||||
|
||||
.weekday-toggles { display: flex; gap: 6px; flex-wrap: wrap; }
|
||||
.weekday-chip {
|
||||
width: 34px; height: 34px; border-radius: 50%;
|
||||
border: 1.5px solid var(--color-border); background: var(--color-surface);
|
||||
font: var(--text-caption); color: var(--color-ink-500);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.weekday-chip.is-selected { background: var(--color-ink-700); border-color: var(--color-ink-700); color: var(--color-accent-text); }
|
||||
|
||||
.recurrence-hint { font: 12px var(--font-body); color: var(--color-ink-500); margin-top: 8px; }
|
||||
|
||||
/* ---------- Доп. состояния (ниже основного грида) ---------- */
|
||||
.states-section { margin-top: var(--space-10); }
|
||||
.states-head { margin-bottom: var(--space-5); }
|
||||
.states-head h2 { font: var(--text-h1); color: var(--color-ink-900); margin: 0 0 4px; }
|
||||
.states-head p { font: var(--text-body-lg); color: var(--color-ink-500); margin: 0; }
|
||||
.states-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-6); align-items: start; }
|
||||
|
||||
.state-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); padding: var(--space-6); }
|
||||
.screen-label { font: var(--text-caption); color: var(--color-ink-400); margin: 0 0 var(--space-4); }
|
||||
.screen-label b { color: var(--color-ink-700); }
|
||||
|
||||
.next-occurrence { display: flex; align-items: center; gap: 8px; background: var(--color-bg-alt); border-radius: var(--radius-md); padding: 10px 14px; font: var(--text-body); color: var(--color-ink-700); font-weight: 600; margin-bottom: var(--space-5); }
|
||||
|
||||
.btn-row { display: flex; gap: var(--space-3); margin-top: var(--space-5); }
|
||||
.btn-row .btn { flex: 1; justify-content: center; }
|
||||
.danger-link-row { text-align: center; margin-top: var(--space-4); }
|
||||
|
||||
.cancel-alert { display: flex; gap: 10px; background: var(--color-warning-bg); border: 1px solid rgba(214,168,61,.35); color: var(--color-warning); border-radius: var(--radius-md); padding: var(--space-4); font: var(--text-body); margin-bottom: var(--space-5); }
|
||||
.cancel-alert strong { display: block; margin-bottom: 2px; color: var(--color-ink-900); }
|
||||
.cancel-options { display: flex; flex-direction: column; gap: var(--space-3); }
|
||||
.cancel-options .btn { width: 100%; justify-content: center; }
|
||||
|
||||
/* ---------- Патч: заливка danger-кнопок отдельным «глубоким» тоном (не совпадает
|
||||
с pastel-текстовым --color-danger, чтобы белый текст держал контраст) ---------- */
|
||||
.btn-danger { background: var(--color-danger-solid); }
|
||||
.btn-danger:hover { background: var(--color-danger-solid-hover); filter: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<svg style="display:none">
|
||||
<symbol id="i-chevron-left" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></symbol>
|
||||
<symbol id="i-chevron-right" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></symbol>
|
||||
<symbol id="i-calendar" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></symbol>
|
||||
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||||
<symbol id="i-alert" viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></symbol>
|
||||
<symbol id="i-plus" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></symbol>
|
||||
<symbol id="i-repeat" viewBox="0 0 24 24"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/></symbol>
|
||||
<symbol id="i-play" viewBox="0 0 24 24"><polygon points="6 3 20 12 6 21 6 3"/></symbol>
|
||||
<symbol id="i-users" viewBox="0 0 24 24"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></symbol>
|
||||
<symbol id="i-x" viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></symbol>
|
||||
<symbol id="i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></symbol>
|
||||
<symbol id="i-moon" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z"/></symbol>
|
||||
</svg>
|
||||
|
||||
<header class="topbar">
|
||||
<div class="brand-mark"><svg class="logo-mark" viewBox="0 0 32 32" fill="none" aria-hidden="true"><polygon points="21,16 18.5,20.3 13.5,20.3 11,16 13.5,11.7 18.5,11.7" fill="currentColor"/><circle cx="16" cy="16" r="13" stroke="currentColor" stroke-width="2.6" stroke-dasharray="12 6"/></svg> VidConf</div>
|
||||
<nav class="topbar-right">
|
||||
<a href="lobby.html" class="nav-link">Лобби</a>
|
||||
<a href="calendar.html" class="nav-link is-active">Календарь</a>
|
||||
<a href="my-conferences.html" class="nav-link">Мои конференции</a>
|
||||
<a href="admin.html" class="nav-link">Админка</a>
|
||||
<div class="theme-toggle" role="group" aria-label="Переключить тему оформления">
|
||||
<button class="theme-toggle-btn" type="button" aria-pressed="false" aria-label="Светлая тема"><svg class="icon"><use href="#i-sun"/></svg></button>
|
||||
<button class="theme-toggle-btn is-active" type="button" aria-pressed="true" aria-label="Тёмная тема"><svg class="icon"><use href="#i-moon"/></svg></button>
|
||||
</div>
|
||||
<button class="avatar-btn"><span class="avatar">АИ</span><span class="user-name">Анна Иванова</span></button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Календарь конференций</h1>
|
||||
<p>Запланируйте разовую встречу или закрепите постоянную конференцию с повторением — рабочая неделя 14–18 июля</p>
|
||||
</div>
|
||||
<button class="btn btn-primary"><svg class="icon" style="width:16px;height:16px"><use href="#i-plus"/></svg>Новая конференция</button>
|
||||
</div>
|
||||
|
||||
<div class="layout-grid">
|
||||
|
||||
<!-- ===================== СЕТКА КОНФЕРЕНЦИЙ ПО ДНЯМ ===================== -->
|
||||
<section class="week-card">
|
||||
<div class="week-toolbar">
|
||||
<div class="date-nav">
|
||||
<button aria-label="Предыдущая неделя"><svg class="icon" style="width:16px;height:16px"><use href="#i-chevron-left"/></svg></button>
|
||||
<span>14 — 18 июля 2026</span>
|
||||
<button aria-label="Следующая неделя"><svg class="icon" style="width:16px;height:16px"><use href="#i-chevron-right"/></svg></button>
|
||||
</div>
|
||||
<div class="view-toggle">
|
||||
<button class="is-active">Неделя</button>
|
||||
<button>Месяц</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="week-grid">
|
||||
|
||||
<div class="day-col">
|
||||
<div class="day-col-head"><span class="dow">ПН</span><span class="dom">14</span></div>
|
||||
<button class="conf-chip">
|
||||
<span class="conf-badge scheduled"><svg class="icon" style="width:11px;height:11px"><use href="#i-clock"/></svg>Запланирована</span>
|
||||
<span class="chip-time">09:00–09:15</span>
|
||||
<span class="chip-title">Дейли команды</span>
|
||||
<span class="chip-meta"><span class="meta-item"><svg class="icon" style="width:12px;height:12px"><use href="#i-users"/></svg>6</span></span>
|
||||
</button>
|
||||
<div class="day-empty">Свободный день</div>
|
||||
</div>
|
||||
|
||||
<div class="day-col is-today">
|
||||
<div class="day-col-head"><span class="dow">ВТ</span><span class="dom">15</span></div>
|
||||
<button class="conf-chip">
|
||||
<span class="conf-badge pinned"><svg class="icon" style="width:11px;height:11px"><use href="#i-repeat"/></svg>Закреплена</span>
|
||||
<span class="chip-time">11:00–11:45</span>
|
||||
<span class="chip-title">Ревью спринта</span>
|
||||
<span class="chip-meta"><span class="meta-item"><svg class="icon" style="width:12px;height:12px"><use href="#i-users"/></svg>8</span></span>
|
||||
</button>
|
||||
<button class="conf-chip">
|
||||
<span class="conf-badge live"><svg class="icon" style="width:11px;height:11px"><use href="#i-play"/></svg>Идёт сейчас</span>
|
||||
<span class="chip-time">14:00–…</span>
|
||||
<span class="chip-title"><svg class="icon" style="width:13px;height:13px;flex-shrink:0"><use href="#i-lock"/></svg>Синк по релизу 2.4</span>
|
||||
<span class="chip-meta"><span class="meta-item"><svg class="icon" style="width:12px;height:12px"><use href="#i-users"/></svg>4</span></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="day-col">
|
||||
<div class="day-col-head"><span class="dow">СР</span><span class="dom">16</span></div>
|
||||
<button class="conf-chip is-cancelled">
|
||||
<span class="conf-badge cancelled"><svg class="icon" style="width:11px;height:11px"><use href="#i-x"/></svg>Отменена</span>
|
||||
<span class="chip-time">10:00–10:30</span>
|
||||
<span class="chip-title">Собес: Frontend</span>
|
||||
</button>
|
||||
<div class="day-empty">Свободный день</div>
|
||||
</div>
|
||||
|
||||
<div class="day-col">
|
||||
<div class="day-col-head"><span class="dow">ЧТ</span><span class="dom">17</span></div>
|
||||
<button class="conf-chip">
|
||||
<span class="conf-badge pinned"><svg class="icon" style="width:11px;height:11px"><use href="#i-repeat"/></svg>Закреплена</span>
|
||||
<span class="chip-time">11:00–11:45</span>
|
||||
<span class="chip-title">Ревью спринта</span>
|
||||
<span class="chip-meta"><span class="meta-item"><svg class="icon" style="width:12px;height:12px"><use href="#i-users"/></svg>8</span></span>
|
||||
</button>
|
||||
<button class="conf-chip">
|
||||
<span class="conf-badge scheduled"><svg class="icon" style="width:11px;height:11px"><use href="#i-clock"/></svg>Запланирована</span>
|
||||
<span class="chip-time">15:00–15:30</span>
|
||||
<span class="chip-title">1:1 с руководителем</span>
|
||||
<span class="chip-meta"><span class="meta-item"><svg class="icon" style="width:12px;height:12px"><use href="#i-users"/></svg>2</span></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="day-col">
|
||||
<div class="day-col-head"><span class="dow">ПТ</span><span class="dom">18</span></div>
|
||||
<button class="conf-chip">
|
||||
<span class="conf-badge scheduled"><svg class="icon" style="width:11px;height:11px"><use href="#i-clock"/></svg>Запланирована</span>
|
||||
<span class="chip-time">12:00–13:00</span>
|
||||
<span class="chip-title"><svg class="icon" style="width:13px;height:13px;flex-shrink:0"><use href="#i-lock"/></svg>Итоги квартала</span>
|
||||
<span class="chip-meta"><span class="meta-item"><svg class="icon" style="width:12px;height:12px"><use href="#i-users"/></svg>14</span></span>
|
||||
</button>
|
||||
<div class="day-empty">Ещё нет конференций</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===================== КАРТОЧКА СОЗДАНИЯ (состояние: создание, пусто) ===================== -->
|
||||
<aside class="create-card" id="create-card">
|
||||
<h2><svg class="icon"><use href="#i-calendar"/></svg>Новая конференция</h2>
|
||||
|
||||
<form>
|
||||
<div class="field">
|
||||
<label>Название</label>
|
||||
<input type="text" placeholder="Например, Синк по релизу 2.4">
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<div class="field"><label>Дата</label><input type="text" value="21.07.2026"></div>
|
||||
<div class="field"><label>Время</label><input type="text" value="10:00–10:30"></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Участники (email через запятую)</label>
|
||||
<textarea rows="2" placeholder="i.sokolov@company.ru, m.kuznetsova@company.ru"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="toggle-block">
|
||||
<div class="toggle-row" style="border-top:none; padding-top:0">
|
||||
<div class="toggle-copy"><strong>Закрытая по паролю</strong><span>Вход только по паролю, включая гостей</span></div>
|
||||
<label class="switch"><input type="checkbox" id="pw-toggle"><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="password-block field">
|
||||
<label><svg class="icon" style="width:14px;height:14px"><use href="#i-lock"/></svg>Пароль конференции</label>
|
||||
<input type="password" placeholder="Минимум 6 символов">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pin-block">
|
||||
<div class="toggle-row">
|
||||
<div class="toggle-copy"><strong>Закрепить постоянную конференцию</strong><span>Сохранится и попадёт в «Мои конференции», не исчезнет после встречи</span></div>
|
||||
<label class="switch"><input type="checkbox" id="pin-toggle"><span class="slider"></span></label>
|
||||
</div>
|
||||
|
||||
<!-- Раскрывается по клику на тумблер выше (CSS :has(), без JS) -->
|
||||
<div class="recurrence-panel">
|
||||
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row">
|
||||
<input type="radio" name="recurrence" checked>
|
||||
По дням недели, еженедельно
|
||||
</label>
|
||||
<div class="recurrence-sub">
|
||||
<div class="weekday-toggles">
|
||||
<span class="weekday-chip">ПН</span>
|
||||
<span class="weekday-chip is-selected">ВТ</span>
|
||||
<span class="weekday-chip">СР</span>
|
||||
<span class="weekday-chip is-selected">ЧТ</span>
|
||||
<span class="weekday-chip">ПТ</span>
|
||||
<span class="weekday-chip">СБ</span>
|
||||
<span class="weekday-chip">ВС</span>
|
||||
</div>
|
||||
<p class="recurrence-hint">Ближайшие: вторник 21 июля, четверг 23 июля</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row">
|
||||
<input type="radio" name="recurrence">
|
||||
Раз в 2 недели
|
||||
</label>
|
||||
<div class="recurrence-sub">
|
||||
<div class="field-row">
|
||||
<div class="field" style="margin-bottom:0"><label>Начиная с</label><input type="text" value="21.07.2026"></div>
|
||||
<div class="field" style="margin-bottom:0"><label>День недели</label><select><option>Вторник</option><option>Среда</option></select></div>
|
||||
</div>
|
||||
<p class="recurrence-hint">Следующие: 4 августа, 18 августа</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row">
|
||||
<input type="radio" name="recurrence">
|
||||
Раз в месяц
|
||||
</label>
|
||||
<div class="recurrence-sub">
|
||||
<div class="field"><label>Число месяца</label><input type="text" value="31" inputmode="numeric"></div>
|
||||
<p class="recurrence-hint">Если в месяце нет такого числа — переносится на последний день месяца (напр. 28/29 февраля)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row">
|
||||
<input type="radio" name="recurrence">
|
||||
Каждые N дней
|
||||
</label>
|
||||
<div class="recurrence-sub">
|
||||
<div class="field-row">
|
||||
<div class="field" style="margin-bottom:0"><label>N (дней)</label><input type="text" value="10" inputmode="numeric"></div>
|
||||
<div class="field" style="margin-bottom:0"><label>Начиная с</label><input type="text" value="21.07.2026"></div>
|
||||
</div>
|
||||
<p class="recurrence-hint">Ближайшие: через 10, 20, 30 дней</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary full" style="margin-top: var(--space-5)">Создать конференцию</button>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ===================== ДОП. СОСТОЯНИЯ: РЕДАКТИРОВАНИЕ И ОТМЕНА ===================== -->
|
||||
<section class="states-section">
|
||||
<div class="states-head">
|
||||
<h2>Другие состояния карточки</h2>
|
||||
<p>Раскрытая карточка с закреплением, редактирование существующей закреплённой конференции и отмена вхождения/всех повторений.</p>
|
||||
</div>
|
||||
|
||||
<div class="states-grid">
|
||||
|
||||
<!-- Состояние: редактирование существующей закреплённой конференции -->
|
||||
<div class="state-card">
|
||||
<p class="screen-label">Состояние: <b>редактирование закреплённой конференции</b></p>
|
||||
|
||||
<div class="next-occurrence">
|
||||
<svg class="icon" style="width:16px;height:16px"><use href="#i-clock"/></svg>
|
||||
Следующее вхождение: вторник, 22 июля, 11:00
|
||||
</div>
|
||||
|
||||
<form>
|
||||
<div class="field"><label>Название</label><input type="text" value="Ревью спринта"></div>
|
||||
<div class="field-row">
|
||||
<div class="field"><label>Время</label><input type="text" value="11:00–11:45"></div>
|
||||
<div class="field"><label>Участники</label><input type="text" value="8 человек"></div>
|
||||
</div>
|
||||
|
||||
<div class="pin-block">
|
||||
<div class="toggle-row" style="border-top:none; padding-top:0">
|
||||
<div class="toggle-copy"><strong>Закреплена</strong><span>Повторяется по расписанию</span></div>
|
||||
<label class="switch"><input type="checkbox" id="pin-toggle-edit" checked><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="recurrence-panel">
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row">
|
||||
<input type="radio" name="recurrence-edit" checked>
|
||||
По дням недели, еженедельно
|
||||
</label>
|
||||
<div class="recurrence-sub">
|
||||
<div class="weekday-toggles">
|
||||
<span class="weekday-chip">ПН</span>
|
||||
<span class="weekday-chip is-selected">ВТ</span>
|
||||
<span class="weekday-chip">СР</span>
|
||||
<span class="weekday-chip is-selected">ЧТ</span>
|
||||
<span class="weekday-chip">ПТ</span>
|
||||
<span class="weekday-chip">СБ</span>
|
||||
<span class="weekday-chip">ВС</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row"><input type="radio" name="recurrence-edit">Раз в 2 недели</label>
|
||||
</div>
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row"><input type="radio" name="recurrence-edit">Раз в месяц</label>
|
||||
</div>
|
||||
<div class="recurrence-option">
|
||||
<label class="recurrence-option-row"><input type="radio" name="recurrence-edit">Каждые N дней</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-row">
|
||||
<button type="submit" class="btn btn-primary">Сохранить изменения</button>
|
||||
<button type="button" class="btn btn-secondary">Открепить</button>
|
||||
</div>
|
||||
<div class="danger-link-row">
|
||||
<button type="button" class="btn-text">Удалить конференцию навсегда</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Состояние: отмена одной / всех повторяющихся -->
|
||||
<div class="state-card">
|
||||
<p class="screen-label">Состояние: <b>отмена вхождения повторяющейся конференции</b></p>
|
||||
|
||||
<div class="cancel-alert">
|
||||
<svg class="icon" style="width:18px;height:18px;flex-shrink:0;margin-top:1px"><use href="#i-alert"/></svg>
|
||||
<div><strong>«Ревью спринта» — вторник, 22 июля, 11:00</strong>Это повторяющаяся конференция (по вторникам и четвергам). Что отменить?</div>
|
||||
</div>
|
||||
|
||||
<div class="cancel-options">
|
||||
<button class="btn btn-secondary"><svg class="icon" style="width:16px;height:16px"><use href="#i-x"/></svg>Отменить только это вхождение</button>
|
||||
<button class="btn btn-danger"><svg class="icon" style="width:16px;height:16px"><use href="#i-x"/></svg>Отменить все повторения</button>
|
||||
</div>
|
||||
<div class="danger-link-row">
|
||||
<button type="button" class="btn-text" style="color: var(--color-ink-500)">Оставить как есть</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
253
design/mockups/dark/join.html
Normal file
253
design/mockups/dark/join.html
Normal file
@@ -0,0 +1,253 @@
|
||||
<!doctype html>
|
||||
<html lang="ru" data-theme="shell-dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>VidConf — Подключение к конференции — тёмная тема, НА СОГЛАСОВАНИИ</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../tokens.css">
|
||||
<style>
|
||||
/* =====================================================================
|
||||
* ТЁМНЫЙ ВАРИАНТ ОБОЛОЧКИ — ПРЕДЛОЖЕНИЕ, НЕ УТВЕРЖДЕНО.
|
||||
* Разметка и контент идентичны design/mockups/join.html 1:1, меняется
|
||||
* только цветовая схема — палитра переиспользует токены комнаты
|
||||
* конференции. Обоснование цветов — design/mockups/dark/README.md.
|
||||
* ===================================================================== */
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
|
||||
--color-bg: #1E1E1E;
|
||||
--color-bg-alt: #242424;
|
||||
--color-surface: #2A2A2A;
|
||||
--color-surface-glass: rgba(51, 51, 51, .55);
|
||||
|
||||
--color-border: #3F3F3F;
|
||||
--color-border-strong: #585858;
|
||||
--color-border-pill: #7FDDA8;
|
||||
|
||||
--color-ink-900: #E8E8E8;
|
||||
--color-ink-700: #7FDDA8;
|
||||
--color-ink-500: #B0B0B0;
|
||||
--color-ink-400: #5D9A78;
|
||||
--color-ink-300: #7A7A7A;
|
||||
|
||||
--color-accent: #7FDDA8;
|
||||
--color-accent-hover: #75CA9A;
|
||||
--color-accent-active: #6CB78C;
|
||||
--color-accent-text: #10331F;
|
||||
|
||||
--color-success: #7FDDA8;
|
||||
--color-success-bg: rgba(127, 221, 168, .14);
|
||||
--color-success-dot: #7FDDA8;
|
||||
|
||||
--color-danger: #EB93A1;
|
||||
--color-danger-bg: rgba(235, 147, 161, .14);
|
||||
--color-danger-dot: #EB93A1;
|
||||
--color-danger-solid: #B85468;
|
||||
--color-danger-solid-hover: #B9495F;
|
||||
|
||||
--color-warning: #D6A83D;
|
||||
--color-warning-bg: rgba(214, 168, 61, .16);
|
||||
--color-warning-dot: #D6A83D;
|
||||
|
||||
--color-focus-ring: #D6A83D;
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, .35);
|
||||
--shadow-md: 0 4px 16px rgba(0, 0, 0, .4);
|
||||
--shadow-lg: 0 12px 32px rgba(0, 0, 0, .5);
|
||||
--shadow-glass: 0 8px 24px rgba(0, 0, 0, .45);
|
||||
}
|
||||
|
||||
.theme-toggle { display: inline-flex; gap: 2px; background: var(--color-bg-alt); padding: 4px; border-radius: var(--radius-full); border: 1px solid var(--color-border); }
|
||||
.theme-toggle-btn { width: 32px; height: 32px; border-radius: 50%; border: none; background: transparent; color: var(--color-ink-500); display: flex; align-items: center; justify-content: center; }
|
||||
.theme-toggle-btn .icon { width: 16px; height: 16px; }
|
||||
.theme-toggle-btn:hover { color: var(--color-ink-900); }
|
||||
.theme-toggle-btn.is-active { background: var(--color-surface); color: var(--color-ink-700); box-shadow: var(--shadow-sm); }
|
||||
|
||||
/*
|
||||
* Подключение к конференции (фаза R1, пивот) — три состояния одного flow:
|
||||
* (а) ввод ссылки/номера, (б) «представиться» для гостя (имя обязательно,
|
||||
* email факультативно — для саммари), (в) пароль закрытой конференции.
|
||||
*/
|
||||
|
||||
html { min-width: 1280px; }
|
||||
body { min-height: 100vh; display: flex; flex-direction: column; background: var(--color-bg); }
|
||||
|
||||
.topbar { display: flex; align-items: center; justify-content: space-between; padding: var(--space-5) var(--space-8); }
|
||||
.brand-mark { display: inline-flex; align-items: center; gap: 8px; font: var(--text-h3); color: var(--color-ink-700); text-decoration: none; }
|
||||
.brand-mark .logo-mark { width: 22px; height: 22px; flex-shrink: 0; }
|
||||
|
||||
main { flex: 1; display: flex; flex-direction: column; align-items: center; padding: var(--space-6) var(--space-8) var(--space-16); gap: var(--space-16); }
|
||||
|
||||
.screen-block { width: 100%; max-width: 440px; }
|
||||
.screen-label { font: var(--text-caption); color: var(--color-ink-400); margin: 0 0 var(--space-3); text-align: center; }
|
||||
.screen-label b { color: var(--color-ink-700); }
|
||||
|
||||
.flow-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); padding: var(--space-8); }
|
||||
.flow-icon { width: 52px; height: 52px; border-radius: var(--radius-full); background: var(--color-bg-alt); color: var(--color-ink-700); display: flex; align-items: center; justify-content: center; margin: 0 auto var(--space-5); }
|
||||
.flow-icon .icon { width: 24px; height: 24px; }
|
||||
.flow-card h1 { font: var(--text-h1); color: var(--color-ink-900); margin: 0 0 var(--space-2); text-align: center; }
|
||||
.flow-card .flow-sub { font: var(--text-body-lg); color: var(--color-ink-500); margin: 0 0 var(--space-6); text-align: center; }
|
||||
|
||||
.field { margin-bottom: var(--space-5); }
|
||||
.field label { display: flex; align-items: center; gap: 6px; font: var(--text-body); font-weight: 600; color: var(--color-ink-900); margin-bottom: var(--space-2); }
|
||||
.field-optional { font-weight: 400; color: var(--color-ink-400); }
|
||||
.input-wrap { position: relative; }
|
||||
.input-wrap .icon { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--color-ink-400); }
|
||||
.input-wrap input {
|
||||
width: 100%; font: var(--text-body-lg); color: var(--color-ink-900);
|
||||
padding: 11px 14px 11px 42px; border-radius: var(--radius-md); border: 1px solid var(--color-border);
|
||||
background: var(--color-surface); outline: none;
|
||||
}
|
||||
.input-wrap input::placeholder { color: var(--color-ink-300); }
|
||||
.input-wrap input:hover { border-color: var(--color-border-strong); }
|
||||
.input-wrap input:focus { border-color: var(--color-focus-ring); box-shadow: 0 0 0 3px rgba(214,168,61,.18); }
|
||||
.input-wrap .toggle-eye { left: auto; right: 14px; background: none; border: none; padding: 0; cursor: pointer; }
|
||||
.field-hint { margin-top: var(--space-2); font: var(--text-body); color: var(--color-ink-500); }
|
||||
.field.has-error input { border-color: var(--color-danger); box-shadow: 0 0 0 3px rgba(235,147,161,.18); }
|
||||
.error-msg { display: flex; align-items: center; gap: 6px; margin-top: var(--space-2); font: var(--text-body); color: var(--color-danger); }
|
||||
|
||||
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; width: 100%; border: none; cursor: pointer; font-family: var(--font-body); transition: background var(--transition-fast), box-shadow var(--transition-fast); }
|
||||
.btn-primary { background: var(--color-accent); color: var(--color-accent-text); font: var(--text-body-lg); font-weight: 700; padding: 13px 24px; border-radius: var(--radius-full); }
|
||||
.btn-primary:hover { background: var(--color-accent-hover); box-shadow: var(--shadow-md); }
|
||||
.btn-primary:active { background: var(--color-accent-active); }
|
||||
.btn-primary[disabled] { background: rgba(122,122,122,.35); color: var(--color-ink-300); cursor: not-allowed; }
|
||||
.btn-secondary { background: transparent; color: var(--color-ink-700); border: 1.5px solid var(--color-border-pill); font: var(--text-body-lg); font-weight: 600; padding: 12px 24px; border-radius: var(--radius-full); }
|
||||
.btn-secondary:hover { background: var(--color-bg-alt); }
|
||||
:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
|
||||
|
||||
.link-back { display: block; text-align: center; margin-top: var(--space-5); font: var(--text-body); font-weight: 600; color: var(--color-ink-500); text-decoration: none; }
|
||||
.link-back:hover { color: var(--color-ink-700); }
|
||||
|
||||
/* Плашка целевой конференции (состояния б/в) */
|
||||
.target-conf { display: flex; align-items: center; gap: 12px; background: var(--color-bg-alt); border-radius: var(--radius-lg); padding: var(--space-4); margin-bottom: var(--space-6); }
|
||||
.target-conf .icon-wrap { width: 40px; height: 40px; border-radius: 50%; background: var(--color-surface); border: 1px solid var(--color-border); color: var(--color-ink-700); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.target-conf strong { display: block; font: var(--text-body); color: var(--color-ink-900); }
|
||||
.target-conf span { font: var(--text-mono-sm); color: var(--color-ink-500); }
|
||||
|
||||
.divider { border: none; border-top: 1px solid var(--color-border); width: 100%; max-width: 720px; margin: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<svg style="display:none">
|
||||
<symbol id="i-link2" viewBox="0 0 24 24"><path d="M9 17H7A5 5 0 0 1 7 7h2"/><path d="M15 7h2a5 5 0 1 1 0 10h-2"/><line x1="8" y1="12" x2="16" y2="12"/></symbol>
|
||||
<symbol id="i-hash" viewBox="0 0 24 24"><line x1="4" y1="9" x2="20" y2="9"/><line x1="4" y1="15" x2="20" y2="15"/><line x1="10" y1="3" x2="8" y2="21"/><line x1="16" y1="3" x2="14" y2="21"/></symbol>
|
||||
<symbol id="i-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M6 21v-2a6 6 0 0 1 12 0v2"/></symbol>
|
||||
<symbol id="i-mail" viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m2 6 10 7 10-7"/></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||||
<symbol id="i-eye" viewBox="0 0 24 24"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></symbol>
|
||||
<symbol id="i-eye-off" viewBox="0 0 24 24"><path d="M3 3l18 18"/><path d="M10.58 10.58a2 2 0 0 0 2.83 2.83"/><path d="M9.88 5.09A9.77 9.77 0 0 1 12 5c6.5 0 10 7 10 7a13.16 13.16 0 0 1-3.06 4.06M6.1 6.1A13.16 13.16 0 0 0 2 12s3.5 7 10 7a9.77 9.77 0 0 0 4.9-1.3"/></symbol>
|
||||
<symbol id="i-arrow-right" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></symbol>
|
||||
<symbol id="i-alert" viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></symbol>
|
||||
<symbol id="i-video" viewBox="0 0 24 24"><path d="M15 6H4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2v-2.5l5 3.5V7l-5 3.5V8a2 2 0 0 0-2-2z"/></symbol>
|
||||
<symbol id="i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></symbol>
|
||||
<symbol id="i-moon" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z"/></symbol>
|
||||
</svg>
|
||||
|
||||
<header class="topbar">
|
||||
<a class="brand-mark" href="lobby.html"><svg class="logo-mark" viewBox="0 0 32 32" fill="none" aria-hidden="true"><polygon points="21,16 18.5,20.3 13.5,20.3 11,16 13.5,11.7 18.5,11.7" fill="currentColor"/><circle cx="16" cy="16" r="13" stroke="currentColor" stroke-width="2.6" stroke-dasharray="12 6"/></svg> VidConf</a>
|
||||
<div class="theme-toggle" role="group" aria-label="Переключить тему оформления">
|
||||
<button class="theme-toggle-btn" type="button" aria-pressed="false" aria-label="Светлая тема"><svg class="icon"><use href="#i-sun"/></svg></button>
|
||||
<button class="theme-toggle-btn is-active" type="button" aria-pressed="true" aria-label="Тёмная тема"><svg class="icon"><use href="#i-moon"/></svg></button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<!-- ===================== (а) ВВОД ССЫЛКИ ИЛИ НОМЕРА ===================== -->
|
||||
<section class="screen-block">
|
||||
<p class="screen-label">Экран: <b>ввод ссылки или номера</b></p>
|
||||
<div class="flow-card">
|
||||
<div class="flow-icon" aria-hidden="true"><svg class="icon"><use href="#i-link2"/></svg></div>
|
||||
<h1>Подключиться к конференции</h1>
|
||||
<p class="flow-sub">Вставьте ссылку-приглашение или введите номер конференции</p>
|
||||
|
||||
<form>
|
||||
<div class="field">
|
||||
<label for="join-input">Ссылка или номер</label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-hash"/></svg>
|
||||
<input id="join-input" type="text" placeholder="vidconf.ru/j/... или 8842 104" value="8842 104">
|
||||
</div>
|
||||
<p class="field-hint">Найдите номер или ссылку в приглашении организатора</p>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Продолжить <svg class="icon"><use href="#i-arrow-right"/></svg></button>
|
||||
</form>
|
||||
<a href="lobby.html" class="link-back">Назад в лобби</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<!-- ===================== (б) ПРЕДСТАВИТЬСЯ (ГОСТЬ) ===================== -->
|
||||
<section class="screen-block">
|
||||
<p class="screen-label">Экран: <b>представиться (гость)</b></p>
|
||||
<div class="flow-card">
|
||||
<div class="target-conf">
|
||||
<span class="icon-wrap" aria-hidden="true"><svg class="icon" style="width:18px;height:18px"><use href="#i-video"/></svg></span>
|
||||
<div><strong>Синк по релизу 2.4</strong><span>№ 8842 104</span></div>
|
||||
</div>
|
||||
|
||||
<div class="flow-icon" aria-hidden="true"><svg class="icon"><use href="#i-user"/></svg></div>
|
||||
<h1>Как вас зовут?</h1>
|
||||
<p class="flow-sub">У вас нет аккаунта VidConf — представьтесь, чтобы войти как гость</p>
|
||||
|
||||
<form>
|
||||
<div class="field">
|
||||
<label for="guest-name">Имя<span class="field-optional">· обязательно</span></label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-user"/></svg>
|
||||
<input id="guest-name" type="text" placeholder="Как к вам обращаться" value="Пётр Волков">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="guest-email">Email<span class="field-optional">· необязательно</span></label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-mail"/></svg>
|
||||
<input id="guest-email" type="email" placeholder="you@mail.ru">
|
||||
</div>
|
||||
<p class="field-hint">Укажите, если хотите получить саммари конференции на почту</p>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Войти в конференцию <svg class="icon"><use href="#i-arrow-right"/></svg></button>
|
||||
</form>
|
||||
<a href="join.html" class="link-back">Назад</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<!-- ===================== (в) ПАРОЛЬ ЗАКРЫТОЙ КОНФЕРЕНЦИИ ===================== -->
|
||||
<section class="screen-block">
|
||||
<p class="screen-label">Экран: <b>пароль закрытой конференции</b> (с состоянием ошибки)</p>
|
||||
<div class="flow-card">
|
||||
<div class="target-conf">
|
||||
<span class="icon-wrap" aria-hidden="true"><svg class="icon" style="width:18px;height:18px"><use href="#i-lock"/></svg></span>
|
||||
<div><strong>Итоги квартала</strong><span>№ 4471 208 · закрытая</span></div>
|
||||
</div>
|
||||
|
||||
<div class="flow-icon" aria-hidden="true"><svg class="icon"><use href="#i-lock"/></svg></div>
|
||||
<h1>Введите пароль</h1>
|
||||
<p class="flow-sub">Эта конференция закрыта — доступ только по паролю от организатора</p>
|
||||
|
||||
<form>
|
||||
<div class="field has-error">
|
||||
<label for="join-pass">Пароль конференции</label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-lock"/></svg>
|
||||
<input id="join-pass" type="password" placeholder="••••••••" value="wrongpass">
|
||||
<button type="button" class="toggle-eye" aria-label="Показать пароль">
|
||||
<svg class="icon"><use href="#i-eye"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<p class="error-msg"><svg class="icon" style="width:16px;height:16px"><use href="#i-alert"/></svg> Неверный пароль, попробуйте ещё раз</p>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Войти <svg class="icon"><use href="#i-arrow-right"/></svg></button>
|
||||
</form>
|
||||
<a href="join.html" class="link-back">Назад</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
403
design/mockups/dark/lobby.html
Normal file
403
design/mockups/dark/lobby.html
Normal file
@@ -0,0 +1,403 @@
|
||||
<!doctype html>
|
||||
<html lang="ru" data-theme="shell-dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>VidConf — Лобби (хаб действий) — тёмная тема, НА СОГЛАСОВАНИИ</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../tokens.css">
|
||||
<style>
|
||||
/* =====================================================================
|
||||
* ТЁМНЫЙ ВАРИАНТ ОБОЛОЧКИ — ПРЕДЛОЖЕНИЕ, НЕ УТВЕРЖДЕНО.
|
||||
* Разметка и контент идентичны design/mockups/lobby.html 1:1, меняется
|
||||
* только цветовая схема — палитра переиспользует токены комнаты
|
||||
* конференции. Обоснование цветов — design/mockups/dark/README.md.
|
||||
* ===================================================================== */
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
|
||||
--color-bg: #1E1E1E;
|
||||
--color-bg-alt: #242424;
|
||||
--color-surface: #2A2A2A;
|
||||
--color-surface-glass: rgba(51, 51, 51, .55);
|
||||
|
||||
--color-border: #3F3F3F;
|
||||
--color-border-strong: #585858;
|
||||
--color-border-pill: #7FDDA8;
|
||||
|
||||
--color-ink-900: #E8E8E8;
|
||||
--color-ink-700: #7FDDA8;
|
||||
--color-ink-500: #B0B0B0;
|
||||
--color-ink-400: #5D9A78;
|
||||
--color-ink-300: #7A7A7A;
|
||||
|
||||
--color-accent: #7FDDA8;
|
||||
--color-accent-hover: #75CA9A;
|
||||
--color-accent-active: #6CB78C;
|
||||
--color-accent-text: #10331F;
|
||||
|
||||
--color-success: #7FDDA8;
|
||||
--color-success-bg: rgba(127, 221, 168, .14);
|
||||
--color-success-dot: #7FDDA8;
|
||||
|
||||
--color-danger: #EB93A1;
|
||||
--color-danger-bg: rgba(235, 147, 161, .14);
|
||||
--color-danger-dot: #EB93A1;
|
||||
--color-danger-solid: #B85468;
|
||||
--color-danger-solid-hover: #B9495F;
|
||||
|
||||
--color-warning: #D6A83D;
|
||||
--color-warning-bg: rgba(214, 168, 61, .16);
|
||||
--color-warning-dot: #D6A83D;
|
||||
|
||||
--color-focus-ring: #D6A83D;
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, .35);
|
||||
--shadow-md: 0 4px 16px rgba(0, 0, 0, .4);
|
||||
--shadow-lg: 0 12px 32px rgba(0, 0, 0, .5);
|
||||
--shadow-glass: 0 8px 24px rgba(0, 0, 0, .45);
|
||||
}
|
||||
|
||||
.theme-toggle { display: inline-flex; gap: 2px; background: var(--color-bg-alt); padding: 4px; border-radius: var(--radius-full); border: 1px solid var(--color-border); }
|
||||
.theme-toggle-btn { width: 32px; height: 32px; border-radius: 50%; border: none; background: transparent; color: var(--color-ink-500); display: flex; align-items: center; justify-content: center; }
|
||||
.theme-toggle-btn .icon { width: 16px; height: 16px; }
|
||||
.theme-toggle-btn:hover { color: var(--color-ink-900); }
|
||||
.theme-toggle-btn.is-active { background: var(--color-surface); color: var(--color-ink-700); box-shadow: var(--shadow-sm); }
|
||||
|
||||
html { height: 100%; min-width: 1280px; }
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background:
|
||||
radial-gradient(1100px 520px at 10% -10%, rgba(214, 168, 61, 0.05), transparent 60%),
|
||||
radial-gradient(900px 480px at 105% 110%, rgba(127, 221, 168, 0.14), transparent 55%),
|
||||
var(--color-bg);
|
||||
}
|
||||
|
||||
/* ---------- Верхняя панель ---------- */
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-5) var(--space-8);
|
||||
}
|
||||
|
||||
.brand-mark { display: inline-flex; align-items: center; gap: 8px; font: var(--text-h3); color: var(--color-ink-700); }
|
||||
.brand-mark .logo-mark { width: 22px; height: 22px; flex-shrink: 0; }
|
||||
|
||||
.topbar-right { display: flex; align-items: center; gap: var(--space-4); }
|
||||
|
||||
.burger {
|
||||
width: 44px; height: 44px;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-ink-700);
|
||||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
|
||||
}
|
||||
.burger:hover { border-color: var(--color-border-strong); box-shadow: var(--shadow-sm); }
|
||||
.burger[aria-expanded="true"] { border-color: var(--color-ink-700); }
|
||||
|
||||
.menu-wrap { position: relative; }
|
||||
.menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 10px);
|
||||
right: 0;
|
||||
min-width: 236px;
|
||||
padding: var(--space-2);
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-lg);
|
||||
display: none;
|
||||
z-index: 30;
|
||||
}
|
||||
.menu.is-open { display: block; }
|
||||
.menu a {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 10px 14px;
|
||||
border-radius: var(--radius-sm);
|
||||
font: var(--text-body-lg); font-weight: 600;
|
||||
color: var(--color-ink-900);
|
||||
text-decoration: none;
|
||||
}
|
||||
.menu a:hover { background: var(--color-bg-alt); color: var(--color-ink-700); }
|
||||
.menu a .icon { color: var(--color-ink-400); }
|
||||
.menu-divider { height: 1px; background: var(--color-border); margin: var(--space-2) 6px; }
|
||||
.menu a.danger { color: var(--color-danger); }
|
||||
.menu a.danger .icon { color: var(--color-danger); }
|
||||
|
||||
.user-pill {
|
||||
display: inline-flex; align-items: center; gap: 10px;
|
||||
padding: 6px 16px 6px 6px;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-full);
|
||||
font: var(--text-body); font-weight: 600; color: var(--color-ink-900);
|
||||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
|
||||
}
|
||||
.user-pill:hover { border-color: var(--color-border-strong); box-shadow: var(--shadow-sm); }
|
||||
.user-pill .avatar {
|
||||
width: 30px; height: 30px; border-radius: 50%;
|
||||
background: var(--color-ink-700); color: var(--color-accent-text);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font: var(--text-caption);
|
||||
}
|
||||
|
||||
/* ---------- Мозаика действий ---------- */
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-6) var(--space-8) var(--space-12);
|
||||
}
|
||||
|
||||
.hub-wrap { display: flex; flex-direction: column; align-items: center; gap: var(--space-8); width: 100%; }
|
||||
.hub-intro { text-align: center; max-width: 560px; }
|
||||
.hub-intro .eyebrow { font: var(--text-caption); color: var(--color-ink-400); margin: 0 0 var(--space-2); }
|
||||
.hub-intro h1 { font: var(--text-display-lg); font-family: var(--font-display); color: var(--color-ink-700); margin: 0; }
|
||||
|
||||
.hub {
|
||||
position: relative;
|
||||
width: min(1420px, 100%);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-rows: repeat(2, 340px);
|
||||
gap: var(--space-8);
|
||||
}
|
||||
|
||||
.action {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-10) var(--space-12) var(--space-8);
|
||||
margin-top: 34px;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-3xl);
|
||||
text-decoration: none;
|
||||
color: var(--color-ink-900);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition:
|
||||
transform var(--transition-base),
|
||||
box-shadow var(--transition-base),
|
||||
border-color var(--transition-base);
|
||||
}
|
||||
.action:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
border-color: var(--color-border-strong);
|
||||
}
|
||||
.action:active { transform: translateY(-1px); }
|
||||
|
||||
.action-icon {
|
||||
position: absolute;
|
||||
top: -34px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 68px; height: 68px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-surface);
|
||||
border: var(--border-thick) solid var(--color-ink-700);
|
||||
color: var(--color-ink-700);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
.action-icon .icon { width: 30px; height: 30px; }
|
||||
|
||||
.action-title {
|
||||
margin: 0;
|
||||
font: 600 40px/1.2 var(--font-display);
|
||||
letter-spacing: 0.01em;
|
||||
color: var(--color-ink-700);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.action-object { margin: 0; font: 600 21px/1.35 var(--font-body); color: var(--color-ink-900); }
|
||||
.action-note { margin: 0; font: 400 17px/1.5 var(--font-body); color: var(--color-ink-500); }
|
||||
|
||||
.action--create {
|
||||
background: var(--color-accent);
|
||||
border-color: transparent;
|
||||
}
|
||||
.action--create:hover { background: var(--color-accent-hover); border-color: transparent; }
|
||||
.action--create:active { background: var(--color-accent-active); }
|
||||
.action--create .action-title { font: 700 54px/1.1 var(--font-display); }
|
||||
.action--create .action-title,
|
||||
.action--create .action-object { color: var(--color-accent-text); }
|
||||
.action--create .action-note { color: var(--color-ink-700); }
|
||||
.action--create .action-icon {
|
||||
background: var(--color-accent);
|
||||
border-color: var(--color-accent-text);
|
||||
color: var(--color-accent-text);
|
||||
}
|
||||
|
||||
/* ---------- Медальон VidConf в центре мозаики ---------- */
|
||||
.hub-logo {
|
||||
position: absolute;
|
||||
top: 373px; left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 156px; height: 156px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-surface);
|
||||
border: var(--border-thick) solid var(--color-ink-700);
|
||||
box-shadow: var(--shadow-lg);
|
||||
display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center;
|
||||
gap: 6px;
|
||||
z-index: 20;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hub-logo .logo-mark {
|
||||
width: 56px; height: 56px;
|
||||
color: var(--color-ink-700);
|
||||
}
|
||||
.hub-logo .logo-word {
|
||||
font: 600 16px/1 var(--font-display);
|
||||
color: var(--color-ink-700);
|
||||
}
|
||||
|
||||
/* ---------- Появление ---------- */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.action, .hub-logo {
|
||||
animation: rise 480ms cubic-bezier(0.22, 0.9, 0.3, 1) backwards;
|
||||
}
|
||||
.action--create { animation-delay: 60ms; }
|
||||
.action--calendar { animation-delay: 140ms; }
|
||||
.action--join { animation-delay: 220ms; }
|
||||
.action--mine { animation-delay: 300ms; }
|
||||
.hub-logo { animation-delay: 420ms; }
|
||||
@keyframes rise {
|
||||
from { opacity: 0; transform: translateY(14px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.hub-logo { animation-name: rise-logo; }
|
||||
@keyframes rise-logo {
|
||||
from { opacity: 0; transform: translate(-50%, calc(-50% + 14px)); }
|
||||
to { opacity: 1; transform: translate(-50%, -50%); }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<svg style="display:none" aria-hidden="true">
|
||||
<symbol id="i-video" viewBox="0 0 24 24"><path d="M15 6H4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2v-2.5l5 3.5V7l-5 3.5V8a2 2 0 0 0-2-2z"/></symbol>
|
||||
<symbol id="i-calendar" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></symbol>
|
||||
<symbol id="i-link2" viewBox="0 0 24 24"><path d="M9 17H7A5 5 0 0 1 7 7h2"/><path d="M15 7h2a5 5 0 1 1 0 10h-2"/><line x1="8" y1="12" x2="16" y2="12"/></symbol>
|
||||
<symbol id="i-user" viewBox="0 0 24 24"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></symbol>
|
||||
<symbol id="i-download" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></symbol>
|
||||
<symbol id="i-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></symbol>
|
||||
<symbol id="i-layout-dashboard" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></symbol>
|
||||
<symbol id="i-logout" viewBox="0 0 24 24"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></symbol>
|
||||
<symbol id="i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></symbol>
|
||||
<symbol id="i-moon" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z"/></symbol>
|
||||
</svg>
|
||||
|
||||
<header class="topbar">
|
||||
<div class="brand-mark"><svg class="logo-mark" viewBox="0 0 32 32" fill="none" aria-hidden="true"><polygon points="21,16 18.5,20.3 13.5,20.3 11,16 13.5,11.7 18.5,11.7" fill="currentColor"/><circle cx="16" cy="16" r="13" stroke="currentColor" stroke-width="2.6" stroke-dasharray="12 6"/></svg> VidConf</div>
|
||||
|
||||
<div class="topbar-right">
|
||||
<div class="theme-toggle" role="group" aria-label="Переключить тему оформления">
|
||||
<button class="theme-toggle-btn" type="button" aria-pressed="false" aria-label="Светлая тема"><svg class="icon"><use href="#i-sun"/></svg></button>
|
||||
<button class="theme-toggle-btn is-active" type="button" aria-pressed="true" aria-label="Тёмная тема"><svg class="icon"><use href="#i-moon"/></svg></button>
|
||||
</div>
|
||||
<button class="user-pill">
|
||||
<span class="avatar" aria-hidden="true">ИИ</span>
|
||||
Иванов Иван
|
||||
</button>
|
||||
<div class="menu-wrap">
|
||||
<button class="burger" id="burger" aria-expanded="false" aria-controls="main-menu" aria-label="Открыть меню">
|
||||
<svg class="icon" viewBox="0 0 24 24"><line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="17" x2="20" y2="17"/></svg>
|
||||
</button>
|
||||
<nav class="menu" id="main-menu" aria-label="Основное меню">
|
||||
<a href="#"><svg class="icon"><use href="#i-user"/></svg> Профиль</a>
|
||||
<a href="admin.html"><svg class="icon"><use href="#i-layout-dashboard"/></svg> Админка</a>
|
||||
<a href="#"><svg class="icon"><use href="#i-download"/></svg> Скачать приложение</a>
|
||||
<a href="#"><svg class="icon"><use href="#i-info"/></svg> О сервисе</a>
|
||||
<div class="menu-divider"></div>
|
||||
<a href="login.html" class="danger"><svg class="icon"><use href="#i-logout"/></svg> Выйти</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="hub-wrap">
|
||||
<div class="hub-intro">
|
||||
<p class="eyebrow">[ ДОБРО ПОЖАЛОВАТЬ В VIDCONF ]</p>
|
||||
<h1>Что делаем сегодня?</h1>
|
||||
</div>
|
||||
|
||||
<div class="hub">
|
||||
|
||||
<a class="action action--create" href="../room.html">
|
||||
<span class="action-icon" aria-hidden="true"><svg class="icon"><use href="#i-video"/></svg></span>
|
||||
<h2 class="action-title">Создать</h2>
|
||||
<p class="action-object">конференцию</p>
|
||||
<p class="action-note">начнётся сразу — вы первый участник</p>
|
||||
</a>
|
||||
|
||||
<a class="action action--calendar" href="calendar.html">
|
||||
<span class="action-icon" aria-hidden="true"><svg class="icon"><use href="#i-calendar"/></svg></span>
|
||||
<h2 class="action-title">Календарь</h2>
|
||||
<p class="action-object">конференций</p>
|
||||
<p class="action-note">запланировать · закрепить · повторять</p>
|
||||
</a>
|
||||
|
||||
<a class="action action--join" href="join.html">
|
||||
<span class="action-icon" aria-hidden="true"><svg class="icon"><use href="#i-link2"/></svg></span>
|
||||
<h2 class="action-title">Подключиться</h2>
|
||||
<p class="action-object">к конференции</p>
|
||||
<p class="action-note">по ссылке или номеру</p>
|
||||
</a>
|
||||
|
||||
<a class="action action--mine" href="my-conferences.html">
|
||||
<span class="action-icon" aria-hidden="true"><svg class="icon"><use href="#i-user"/></svg></span>
|
||||
<h2 class="action-title">Мои</h2>
|
||||
<p class="action-object">конференции</p>
|
||||
<p class="action-note">закреплённые и предстоящие</p>
|
||||
</a>
|
||||
|
||||
<div class="hub-logo" aria-hidden="true">
|
||||
<svg class="logo-mark" viewBox="0 0 64 64" fill="none" aria-hidden="true">
|
||||
<polygon points="41,32 36.5,39.8 27.5,39.8 23,32 27.5,24.2 36.5,24.2" fill="currentColor"/>
|
||||
<circle cx="32" cy="32" r="15" stroke="currentColor" stroke-width="3" stroke-dasharray="16 7 28 9 12 16" stroke-dashoffset="5"/>
|
||||
<circle cx="32" cy="32" r="20" stroke="currentColor" stroke-width="2" stroke-dasharray="22 10 8 14 34 12 6 10" stroke-dashoffset="40"/>
|
||||
<circle cx="32" cy="32" r="25" stroke="currentColor" stroke-width="3.5" stroke-dasharray="40 14 12 10 26 16 20 8" stroke-dashoffset="80"/>
|
||||
<circle cx="32" cy="32" r="29" stroke="currentColor" stroke-width="1.6" stroke-dasharray="10 6 60 20 30 10 24 8" stroke-dashoffset="120"/>
|
||||
</svg>
|
||||
<span class="logo-word">VidConf</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Бургер-меню: открытие/закрытие, клик мимо и Escape закрывают.
|
||||
const burger = document.getElementById('burger');
|
||||
const menu = document.getElementById('main-menu');
|
||||
|
||||
function setMenu(open) {
|
||||
menu.classList.toggle('is-open', open);
|
||||
burger.setAttribute('aria-expanded', String(open));
|
||||
burger.setAttribute('aria-label', open ? 'Закрыть меню' : 'Открыть меню');
|
||||
}
|
||||
|
||||
burger.addEventListener('click', () => setMenu(!menu.classList.contains('is-open')));
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('.menu-wrap')) setMenu(false);
|
||||
});
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') { setMenu(false); burger.focus(); }
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
401
design/mockups/dark/login.html
Normal file
401
design/mockups/dark/login.html
Normal file
@@ -0,0 +1,401 @@
|
||||
<!doctype html>
|
||||
<html lang="ru" data-theme="shell-dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>VidConf — Вход / Регистрация / Подтверждение email (тёмная тема, НА СОГЛАСОВАНИИ)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../tokens.css">
|
||||
<style>
|
||||
/* =====================================================================
|
||||
* ТЁМНЫЙ ВАРИАНТ ОБОЛОЧКИ — ПРЕДЛОЖЕНИЕ, НЕ УТВЕРЖДЕНО.
|
||||
* Разметка и контент идентичны design/mockups/auth.html 1:1, меняется
|
||||
* только цветовая схема. Палитра переиспользует токены комнаты
|
||||
* конференции (tokens.css, [data-theme="room"]) — единая тёмная гамма:
|
||||
* графит + пастельная мята/розово-красный/жёлто-оранжевый.
|
||||
* Происхождение цветов и обоснование — design/mockups/dark/README.md.
|
||||
* Технически: блок :root ниже переопределяет одноимённые переменные
|
||||
* оболочки из tokens.css (та же специфичность :root, но позже в
|
||||
* источнике — выигрывает), поэтому весь остальной CSS ниже скопирован
|
||||
* из auth.html БЕЗ ИЗМЕНЕНИЙ и автоматически перекрашивается.
|
||||
* ===================================================================== */
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
|
||||
--color-bg: #1E1E1E; /* = --color-room-bg */
|
||||
--color-bg-alt: #242424; /* новый: между bg и surface */
|
||||
--color-surface: #2A2A2A; /* = --color-room-surface */
|
||||
--color-surface-glass: rgba(51, 51, 51, .55); /* на основе --color-room-surface-raised */
|
||||
|
||||
--color-border: #3F3F3F; /* = --color-room-tile-border */
|
||||
--color-border-strong: #585858; /* новый, hover-обводка */
|
||||
--color-border-pill: #7FDDA8; /* = --color-room-mic-on */
|
||||
|
||||
--color-ink-900: #E8E8E8; /* = --color-room-text-primary */
|
||||
--color-ink-700: #7FDDA8; /* = --color-room-mic-on — бренд/заголовки/ссылки */
|
||||
--color-ink-500: #B0B0B0; /* = --color-room-text-secondary */
|
||||
--color-ink-400: #5D9A78; /* новый, приглушённая мята, крупный текст ⚠︎ */
|
||||
--color-ink-300: #7A7A7A; /* = --color-room-text-tertiary */
|
||||
|
||||
--color-accent: #7FDDA8; /* = --color-room-mic-on */
|
||||
--color-accent-hover: #75CA9A; /* новый */
|
||||
--color-accent-active: #6CB78C; /* новый */
|
||||
--color-accent-text: #10331F; /* новый, тёмный ink (прецедент room.html) */
|
||||
|
||||
--color-success: #7FDDA8;
|
||||
--color-success-bg: rgba(127, 221, 168, .14);
|
||||
--color-success-dot: #7FDDA8;
|
||||
|
||||
--color-danger: #EB93A1; /* = --color-room-mic-off */
|
||||
--color-danger-bg: rgba(235, 147, 161, .14);
|
||||
--color-danger-dot: #EB93A1;
|
||||
--color-danger-solid: #B85468; /* = --color-room-danger-bg, заливка danger-кнопок */
|
||||
--color-danger-solid-hover: #B9495F;
|
||||
|
||||
--color-warning: #D6A83D; /* = --color-room-speaker-ring */
|
||||
--color-warning-bg: rgba(214, 168, 61, .16);
|
||||
--color-warning-dot: #D6A83D;
|
||||
|
||||
--color-focus-ring: #D6A83D; /* = --color-room-focus-ring */
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, .35);
|
||||
--shadow-md: 0 4px 16px rgba(0, 0, 0, .4);
|
||||
--shadow-lg: 0 12px 32px rgba(0, 0, 0, .5);
|
||||
--shadow-glass: 0 8px 24px rgba(0, 0, 0, .45);
|
||||
}
|
||||
|
||||
/* Переключатель темы (солнце/луна) — статичная демонстрация, не функциональна в макете */
|
||||
.theme-toggle { position: fixed; top: 24px; right: 32px; z-index: 50; display: inline-flex; gap: 2px; background: var(--color-bg-alt); padding: 4px; border-radius: var(--radius-full); border: 1px solid var(--color-border); box-shadow: var(--shadow-md); }
|
||||
.theme-toggle-btn { width: 32px; height: 32px; border-radius: 50%; border: none; background: transparent; color: var(--color-ink-500); display: flex; align-items: center; justify-content: center; }
|
||||
.theme-toggle-btn .icon { width: 16px; height: 16px; }
|
||||
.theme-toggle-btn:hover { color: var(--color-ink-900); }
|
||||
.theme-toggle-btn.is-active { background: var(--color-surface); color: var(--color-ink-700); box-shadow: var(--shadow-sm); }
|
||||
|
||||
html { min-width: 1280px; }
|
||||
body { min-height: 100vh; }
|
||||
|
||||
.layout { display: flex; min-height: 100vh; }
|
||||
|
||||
/* ---------- Брендовая панель ---------- */
|
||||
.brand-panel {
|
||||
width: 42%;
|
||||
background: linear-gradient(160deg, #242424 0%, #1E1E1E 55%, #1A1A1A 100%);
|
||||
padding: var(--space-12) var(--space-12);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-right: 1px solid var(--color-border);
|
||||
}
|
||||
.brand-panel::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -120px; bottom: -120px;
|
||||
width: 380px; height: 380px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle at 30% 30%, rgba(127,221,168,.16), rgba(127,221,168,0) 70%);
|
||||
}
|
||||
.brand-mark {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font: var(--text-h3);
|
||||
color: var(--color-ink-700);
|
||||
z-index: 1;
|
||||
}
|
||||
.brand-mark .logo-mark { width: 22px; height: 22px; flex-shrink: 0; }
|
||||
|
||||
.brand-copy { z-index: 1; }
|
||||
.eyebrow {
|
||||
font: var(--text-caption);
|
||||
letter-spacing: .08em;
|
||||
color: var(--color-ink-400);
|
||||
margin: 0 0 var(--space-4);
|
||||
}
|
||||
.brand-headline {
|
||||
font: var(--text-display-lg);
|
||||
font-family: var(--font-display);
|
||||
color: var(--color-ink-700);
|
||||
margin: 0 0 var(--space-4);
|
||||
max-width: 460px;
|
||||
}
|
||||
.brand-sub {
|
||||
font: var(--text-body-lg);
|
||||
color: var(--color-ink-500);
|
||||
max-width: 420px;
|
||||
margin: 0;
|
||||
}
|
||||
.brand-stats { display: flex; gap: var(--space-4); z-index: 1; margin-top: var(--space-8); }
|
||||
.stat-glass {
|
||||
background: var(--color-surface-glass);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255,255,255,.08);
|
||||
box-shadow: var(--shadow-glass);
|
||||
border-radius: var(--radius-xl);
|
||||
padding: var(--space-4) var(--space-5);
|
||||
min-width: 140px;
|
||||
}
|
||||
.stat-glass strong { display: block; font: var(--text-h1); font-family: var(--font-display); color: var(--color-ink-700); }
|
||||
.stat-glass span { font: var(--text-caption); color: var(--color-ink-500); text-transform: none; letter-spacing: normal; }
|
||||
|
||||
/* ---------- Форма-панель ---------- */
|
||||
.form-panel {
|
||||
width: 58%;
|
||||
padding: var(--space-12) var(--space-16);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-16);
|
||||
}
|
||||
.screen-block { max-width: 440px; }
|
||||
.screen-label {
|
||||
font: var(--text-caption);
|
||||
color: var(--color-ink-400);
|
||||
margin: 0 0 var(--space-3);
|
||||
}
|
||||
.screen-label b { color: var(--color-ink-700); }
|
||||
.screen-title { font: var(--text-h1); color: var(--color-ink-900); margin: 0 0 var(--space-8); }
|
||||
|
||||
.divider { border: none; border-top: 1px solid var(--color-border); max-width: 720px; margin: 0; }
|
||||
|
||||
.tabs { display: flex; gap: var(--space-2); margin-bottom: var(--space-6); background: var(--color-bg-alt); padding: 4px; border-radius: var(--radius-full); width: fit-content; }
|
||||
.tab { padding: 8px 20px; border-radius: var(--radius-full); border: none; background: transparent; font: var(--text-body); color: var(--color-ink-500); }
|
||||
.tab.is-active { background: var(--color-surface); color: var(--color-ink-700); font-weight: 600; box-shadow: var(--shadow-sm); }
|
||||
|
||||
.field { margin-bottom: var(--space-5); }
|
||||
.field label { display: block; font: var(--text-body); font-weight: 600; color: var(--color-ink-900); margin-bottom: var(--space-2); }
|
||||
.input-wrap { position: relative; }
|
||||
.input-wrap .icon { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--color-ink-400); }
|
||||
.input-wrap input {
|
||||
width: 100%;
|
||||
font: var(--text-body-lg);
|
||||
color: var(--color-ink-900);
|
||||
padding: 11px 14px 11px 42px;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-surface);
|
||||
outline: none;
|
||||
}
|
||||
.input-wrap input::placeholder { color: var(--color-ink-300); }
|
||||
.input-wrap input:hover { border-color: var(--color-border-strong); }
|
||||
.input-wrap input:focus { border-color: var(--color-focus-ring); box-shadow: 0 0 0 3px rgba(214,168,61,.18); }
|
||||
.input-wrap .toggle-eye { left: auto; right: 14px; background: none; border: none; padding: 0; cursor: pointer; }
|
||||
|
||||
.field.has-error input { border-color: var(--color-danger); box-shadow: 0 0 0 3px rgba(235,147,161,.18); }
|
||||
.error-msg { display: flex; align-items: center; gap: 6px; margin-top: var(--space-2); font: var(--text-body); color: var(--color-danger); }
|
||||
.hint-msg { margin-top: var(--space-2); font: var(--text-body); color: var(--color-ink-500); }
|
||||
|
||||
.field-disabled input { background: var(--color-bg-alt); color: var(--color-ink-300); cursor: not-allowed; }
|
||||
|
||||
.row-between { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-6); }
|
||||
.checkbox-row { display: flex; align-items: center; gap: 8px; font: var(--text-body); color: var(--color-ink-500); }
|
||||
.link { color: var(--color-ink-700); font-weight: 600; text-decoration: none; }
|
||||
.link:hover { text-decoration: underline; }
|
||||
|
||||
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; border: none; cursor: pointer; font-family: var(--font-body); transition: background var(--transition-fast), box-shadow var(--transition-fast); }
|
||||
.btn-primary {
|
||||
width: 100%;
|
||||
background: var(--color-accent);
|
||||
color: var(--color-accent-text);
|
||||
font: var(--text-body-lg);
|
||||
font-weight: 700;
|
||||
padding: 13px 24px;
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
.btn-primary:hover { background: var(--color-accent-hover); box-shadow: var(--shadow-md); }
|
||||
.btn-primary:active { background: var(--color-accent-active); }
|
||||
.btn-primary[disabled] { background: rgba(122,122,122,.35); color: var(--color-ink-300); cursor: not-allowed; }
|
||||
|
||||
.btn-secondary {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
color: var(--color-ink-700);
|
||||
border: 1.5px solid var(--color-border-pill);
|
||||
font: var(--text-body-lg);
|
||||
font-weight: 600;
|
||||
padding: 12px 24px;
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
.btn-secondary:hover { background: var(--color-bg-alt); border-color: var(--color-ink-700); }
|
||||
|
||||
.oauth-row { display: flex; gap: var(--space-3); margin-top: var(--space-5); }
|
||||
.oauth-row .btn-secondary { flex: 1; }
|
||||
|
||||
.separator { display: flex; align-items: center; gap: 12px; color: var(--color-ink-400); font: var(--text-caption); margin: var(--space-6) 0; text-transform: none; letter-spacing: normal; }
|
||||
.separator::before, .separator::after { content: ""; flex: 1; height: 1px; background: var(--color-border); }
|
||||
|
||||
/* ---------- Подтверждение email ---------- */
|
||||
.confirm-card {
|
||||
max-width: 440px;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-2xl);
|
||||
padding: var(--space-8);
|
||||
text-align: left;
|
||||
}
|
||||
.confirm-icon {
|
||||
width: 56px; height: 56px; border-radius: var(--radius-full);
|
||||
background: var(--color-success-bg); color: var(--color-success);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
.confirm-icon .icon { width: 28px; height: 28px; }
|
||||
.confirm-card h3 { font: var(--text-h2); color: var(--color-ink-900); margin: 0 0 var(--space-2); }
|
||||
.confirm-card p { font: var(--text-body-lg); color: var(--color-ink-500); margin: 0 0 var(--space-6); }
|
||||
.confirm-card .mail-chip {
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
background: var(--color-bg-alt); border-radius: var(--radius-full);
|
||||
padding: 6px 14px; font: var(--text-mono-sm); color: var(--color-ink-700);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
/* pending confirmation state variant */
|
||||
.confirm-card.pending .confirm-icon { background: var(--color-warning-bg); color: var(--color-warning); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<svg style="display:none">
|
||||
<symbol id="i-mail" viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m2 6 10 7 10-7"/></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||||
<symbol id="i-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M6 21v-2a6 6 0 0 1 12 0v2"/></symbol>
|
||||
<symbol id="i-eye" viewBox="0 0 24 24"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></symbol>
|
||||
<symbol id="i-eye-off" viewBox="0 0 24 24"><path d="M3 3l18 18"/><path d="M10.58 10.58a2 2 0 0 0 2.83 2.83"/><path d="M9.88 5.09A9.77 9.77 0 0 1 12 5c6.5 0 10 7 10 7a13.16 13.16 0 0 1-3.06 4.06M6.1 6.1A13.16 13.16 0 0 0 2 12s3.5 7 10 7a9.77 9.77 0 0 0 4.9-1.3"/></symbol>
|
||||
<symbol id="i-check-circle" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></symbol>
|
||||
<symbol id="i-arrow-right" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></symbol>
|
||||
<symbol id="i-alert" viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></symbol>
|
||||
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></symbol>
|
||||
<symbol id="i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></symbol>
|
||||
<symbol id="i-moon" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z"/></symbol>
|
||||
</svg>
|
||||
|
||||
<div class="theme-toggle" role="group" aria-label="Переключить тему оформления">
|
||||
<button class="theme-toggle-btn" type="button" aria-pressed="false" aria-label="Светлая тема"><svg class="icon"><use href="#i-sun"/></svg></button>
|
||||
<button class="theme-toggle-btn is-active" type="button" aria-pressed="true" aria-label="Тёмная тема"><svg class="icon"><use href="#i-moon"/></svg></button>
|
||||
</div>
|
||||
|
||||
<div class="layout">
|
||||
|
||||
<!-- ===================== БРЕНД-ПАНЕЛЬ ===================== -->
|
||||
<aside class="brand-panel">
|
||||
<div class="brand-mark"><svg class="logo-mark" viewBox="0 0 32 32" fill="none" aria-hidden="true"><polygon points="21,16 18.5,20.3 13.5,20.3 11,16 13.5,11.7 18.5,11.7" fill="currentColor"/><circle cx="16" cy="16" r="13" stroke="currentColor" stroke-width="2.6" stroke-dasharray="12 6"/></svg> VidConf</div>
|
||||
<div class="brand-copy">
|
||||
<p class="eyebrow">[ SELF-HOSTED ВИДЕОКОНФЕРЕНЦИИ ]</p>
|
||||
<h1 class="brand-headline">Ваши встречи.<br>Ваша инфраструктура.</h1>
|
||||
<p class="brand-sub">Видео, записи и AI-саммари обрабатываются на ваших серверах - данные не уходят
|
||||
за пределы вашей инфраструктуры. Гости подключаются по ссылке или номеру, без регистрации.</p>
|
||||
<div class="brand-stats">
|
||||
<div class="stat-glass"><strong>100%</strong><span>данных - в вашем контуре</span></div>
|
||||
<div class="stat-glass"><strong>AI-саммари</strong><span>на почту после встречи</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint-msg" style="z-index:1">© 2026 VidConf · self-hosted</p>
|
||||
</aside>
|
||||
|
||||
<!-- ===================== ФОРМА: ВХОД / РЕГИСТРАЦИЯ ===================== -->
|
||||
<main class="form-panel">
|
||||
|
||||
<section class="screen-block">
|
||||
<p class="screen-label">Экран: <b>вход в аккаунт</b></p>
|
||||
<div class="tabs" role="tablist" aria-label="Способ авторизации">
|
||||
<button class="tab is-active" role="tab" aria-selected="true">Вход</button>
|
||||
<button class="tab" role="tab" aria-selected="false">Регистрация</button>
|
||||
</div>
|
||||
|
||||
<form>
|
||||
<div class="field">
|
||||
<label for="login-email">Рабочая почта</label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-mail"/></svg>
|
||||
<input id="login-email" type="email" placeholder="you@company.ru" value="a.ivanova@vidconf.dev">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field has-error">
|
||||
<label for="login-pass">Пароль</label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-lock"/></svg>
|
||||
<input id="login-pass" type="password" placeholder="••••••••" value="wrongpass">
|
||||
<button type="button" class="toggle-eye" aria-label="Показать пароль">
|
||||
<svg class="icon"><use href="#i-eye"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<p class="error-msg"><svg class="icon" style="width:16px;height:16px"><use href="#i-alert"/></svg> Неверный email или пароль</p>
|
||||
</div>
|
||||
|
||||
<div class="row-between">
|
||||
<label class="checkbox-row"><input type="checkbox" checked> Запомнить меня</label>
|
||||
<a href="#" class="link">Забыли пароль?</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Войти <svg class="icon"><use href="#i-arrow-right"/></svg></button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<section class="screen-block">
|
||||
<p class="screen-label">Экран: <b>регистрация</b></p>
|
||||
<h2 class="screen-title">Создать аккаунт</h2>
|
||||
<form>
|
||||
<div class="field">
|
||||
<label for="reg-name">Имя и фамилия</label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-user"/></svg>
|
||||
<input id="reg-name" type="text" placeholder="Мария Кузнецова">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="reg-email">Рабочая почта</label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-mail"/></svg>
|
||||
<input id="reg-email" type="email" placeholder="you@company.ru">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="reg-pass">Пароль</label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-lock"/></svg>
|
||||
<input id="reg-pass" type="password" placeholder="Минимум 8 символов">
|
||||
<button type="button" class="toggle-eye" aria-label="Скрыть пароль">
|
||||
<svg class="icon"><use href="#i-eye-off"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<p class="hint-msg">Минимум 8 символов, буквы и цифры</p>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" style="margin-top:8px">Зарегистрироваться</button>
|
||||
<div class="separator">или</div>
|
||||
<div class="field field-disabled" style="margin-bottom:0">
|
||||
<label for="reg-invite">Код приглашения (недоступно без администратора)</label>
|
||||
<div class="input-wrap">
|
||||
<input id="reg-invite" type="text" placeholder="INVITE-XXXX-XXXX" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<section class="screen-block">
|
||||
<p class="screen-label">Экран: <b>подтверждение email</b></p>
|
||||
|
||||
<div class="confirm-card" style="margin-bottom: var(--space-6);">
|
||||
<div class="confirm-icon"><svg class="icon"><use href="#i-check-circle"/></svg></div>
|
||||
<h3>Почта подтверждена</h3>
|
||||
<p>Аккаунт <strong>a.ivanova@vidconf.dev</strong> активирован. Теперь
|
||||
доступно бронирование переговорных и вход в конференции.</p>
|
||||
<button class="btn btn-primary">Перейти в лобби <svg class="icon"><use href="#i-arrow-right"/></svg></button>
|
||||
</div>
|
||||
|
||||
<div class="confirm-card pending">
|
||||
<div class="confirm-icon"><svg class="icon"><use href="#i-clock"/></svg></div>
|
||||
<h3>Подтвердите почту</h3>
|
||||
<p>Мы отправили письмо со ссылкой на <span class="mail-chip"><svg class="icon" style="width:14px;height:14px"><use href="#i-mail"/></svg>m.kuznetsova@company.ru</span></p>
|
||||
<button class="btn btn-secondary">Отправить письмо повторно</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
403
design/mockups/dark/my-conferences.html
Normal file
403
design/mockups/dark/my-conferences.html
Normal file
@@ -0,0 +1,403 @@
|
||||
<!doctype html>
|
||||
<html lang="ru" data-theme="shell-dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>VidConf — Мои конференции — тёмная тема, НА СОГЛАСОВАНИИ</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../tokens.css">
|
||||
<style>
|
||||
/* =====================================================================
|
||||
* ТЁМНЫЙ ВАРИАНТ ОБОЛОЧКИ — ПРЕДЛОЖЕНИЕ, НЕ УТВЕРЖДЕНО.
|
||||
* Разметка и контент идентичны design/mockups/my-conferences.html 1:1,
|
||||
* меняется только цветовая схема — палитра переиспользует токены
|
||||
* комнаты конференции. Обоснование цветов — design/mockups/dark/README.md.
|
||||
* ===================================================================== */
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
|
||||
--color-bg: #1E1E1E;
|
||||
--color-bg-alt: #242424;
|
||||
--color-surface: #2A2A2A;
|
||||
--color-surface-glass: rgba(51, 51, 51, .55);
|
||||
|
||||
--color-border: #3F3F3F;
|
||||
--color-border-strong: #585858;
|
||||
--color-border-pill: #7FDDA8;
|
||||
|
||||
--color-ink-900: #E8E8E8;
|
||||
--color-ink-700: #7FDDA8;
|
||||
--color-ink-500: #B0B0B0;
|
||||
--color-ink-400: #5D9A78;
|
||||
--color-ink-300: #7A7A7A;
|
||||
|
||||
--color-accent: #7FDDA8;
|
||||
--color-accent-hover: #75CA9A;
|
||||
--color-accent-active: #6CB78C;
|
||||
--color-accent-text: #10331F;
|
||||
|
||||
--color-success: #7FDDA8;
|
||||
--color-success-bg: rgba(127, 221, 168, .14);
|
||||
--color-success-dot: #7FDDA8;
|
||||
|
||||
--color-danger: #EB93A1;
|
||||
--color-danger-bg: rgba(235, 147, 161, .14);
|
||||
--color-danger-dot: #EB93A1;
|
||||
--color-danger-solid: #B85468;
|
||||
--color-danger-solid-hover: #B9495F;
|
||||
|
||||
--color-warning: #D6A83D;
|
||||
--color-warning-bg: rgba(214, 168, 61, .16);
|
||||
--color-warning-dot: #D6A83D;
|
||||
|
||||
--color-focus-ring: #D6A83D;
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, .35);
|
||||
--shadow-md: 0 4px 16px rgba(0, 0, 0, .4);
|
||||
--shadow-lg: 0 12px 32px rgba(0, 0, 0, .5);
|
||||
--shadow-glass: 0 8px 24px rgba(0, 0, 0, .45);
|
||||
}
|
||||
|
||||
.theme-toggle { display: inline-flex; gap: 2px; background: var(--color-bg-alt); padding: 4px; border-radius: var(--radius-full); border: 1px solid var(--color-border); }
|
||||
.theme-toggle-btn { width: 32px; height: 32px; border-radius: 50%; border: none; background: transparent; color: var(--color-ink-500); display: flex; align-items: center; justify-content: center; }
|
||||
.theme-toggle-btn .icon { width: 16px; height: 16px; }
|
||||
.theme-toggle-btn:hover { color: var(--color-ink-900); }
|
||||
.theme-toggle-btn.is-active { background: var(--color-surface); color: var(--color-ink-700); box-shadow: var(--shadow-sm); }
|
||||
|
||||
html { min-width: 1280px; }
|
||||
|
||||
.topbar {
|
||||
position: sticky; top: 0; z-index: 20;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: var(--space-4) var(--space-8);
|
||||
background: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.brand-mark { display: inline-flex; align-items: center; gap: 8px; font: var(--text-h3); color: var(--color-ink-700); }
|
||||
.brand-mark .logo-mark { width: 22px; height: 22px; flex-shrink: 0; }
|
||||
.topbar-right { display: flex; align-items: center; gap: var(--space-5); }
|
||||
.nav-link { font: var(--text-body); font-weight: 600; color: var(--color-ink-500); text-decoration: none; }
|
||||
.nav-link.is-active { color: var(--color-ink-700); }
|
||||
.avatar-btn { display: flex; align-items: center; gap: 8px; background: var(--color-bg-alt); border: 1px solid var(--color-border); border-radius: var(--radius-full); padding: 6px 14px 6px 6px; }
|
||||
.avatar { width: 28px; height: 28px; border-radius: 50%; background: var(--color-ink-700); color: var(--color-accent-text); display:flex; align-items:center; justify-content:center; font: var(--text-caption); text-transform: none; letter-spacing: 0; }
|
||||
.avatar-btn .user-name { font: var(--text-body); font-weight: 600; color: var(--color-ink-900); }
|
||||
|
||||
main { padding: var(--space-8); max-width: 1280px; margin: 0 auto; }
|
||||
|
||||
.page-head { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: var(--space-6); }
|
||||
.page-head h1 { font: var(--text-display-lg); font-family: var(--font-display); color: var(--color-ink-700); margin: 0 0 6px; }
|
||||
.page-head p { font: var(--text-body-lg); color: var(--color-ink-500); margin: 0; }
|
||||
|
||||
.btn { display: inline-flex; align-items: center; gap: 8px; border: none; cursor: pointer; font-family: var(--font-body); white-space: nowrap; transition: background var(--transition-fast), box-shadow var(--transition-fast); }
|
||||
.btn-primary { background: var(--color-accent); color: var(--color-accent-text); font: var(--text-body); font-weight: 700; padding: 11px 22px; border-radius: var(--radius-full); }
|
||||
.btn-primary:hover { background: var(--color-accent-hover); box-shadow: var(--shadow-md); }
|
||||
.btn-primary:active { background: var(--color-accent-active); }
|
||||
.btn-secondary { background: transparent; color: var(--color-ink-700); border: 1.5px solid var(--color-border-pill); font: var(--text-body); font-weight: 600; padding: 9px 18px; border-radius: var(--radius-full); }
|
||||
.btn-secondary:hover { background: var(--color-bg-alt); }
|
||||
:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
|
||||
|
||||
.filters { display: flex; gap: 8px; margin-bottom: var(--space-6); }
|
||||
.chip { display: inline-flex; align-items: center; gap: 6px; padding: 9px 16px; border-radius: var(--radius-full); border: 1.5px solid var(--color-border); background: var(--color-surface); font: var(--text-body); font-weight: 600; color: var(--color-ink-500); }
|
||||
.chip.is-active { border-color: var(--color-ink-700); color: var(--color-ink-700); background: var(--color-bg-alt); }
|
||||
|
||||
.conf-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-5); }
|
||||
|
||||
.conf-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); padding: var(--space-6); display: flex; flex-direction: column; gap: var(--space-4); transition: box-shadow var(--transition-base), border-color var(--transition-base); }
|
||||
.conf-card:hover { box-shadow: var(--shadow-md); border-color: var(--color-border-strong); }
|
||||
.conf-card.is-ended { opacity: .85; }
|
||||
|
||||
.conf-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); }
|
||||
.conf-title-wrap h3 { margin: 0 0 4px; font: var(--text-h3); color: var(--color-ink-900); display: flex; align-items: center; gap: 6px; }
|
||||
.conf-title-wrap p { margin: 0; font: var(--text-body); color: var(--color-ink-500); }
|
||||
|
||||
.conf-badge { display: inline-flex; align-items: center; gap: 5px; padding: 4px 12px; border-radius: var(--radius-full); font: var(--text-caption); white-space: nowrap; }
|
||||
.conf-badge.pinned { background: var(--color-bg-alt); color: var(--color-ink-700); border: 1px solid var(--color-border-strong); }
|
||||
.conf-badge.scheduled { background: var(--color-warning-bg); color: var(--color-warning); }
|
||||
.conf-badge.ended { background: var(--color-bg-alt); color: var(--color-ink-500); }
|
||||
|
||||
.conf-meta-row { display: flex; align-items: center; gap: var(--space-4); font: var(--text-body); color: var(--color-ink-500); }
|
||||
.conf-meta-row .meta-item { display: flex; align-items: center; gap: 6px; }
|
||||
|
||||
.pills-row { display: flex; gap: var(--space-3); }
|
||||
.pill-group { flex: 1; min-width: 0; }
|
||||
.pill-label { display: block; font: var(--text-caption); color: var(--color-ink-400); margin-bottom: 4px; }
|
||||
.code-pill { display: flex; align-items: center; gap: 6px; background: var(--color-bg-alt); border: 1px solid var(--color-border); border-radius: var(--radius-full); padding: 6px 6px 6px 14px; }
|
||||
.pill-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: var(--text-mono-sm); color: var(--color-ink-700); }
|
||||
.copy-btn { position: relative; width: 28px; height: 28px; flex-shrink: 0; border-radius: 50%; border: none; background: var(--color-surface); color: var(--color-ink-500); display: flex; align-items: center; justify-content: center; transition: color var(--transition-fast), box-shadow var(--transition-fast); }
|
||||
.copy-btn:hover { color: var(--color-ink-700); box-shadow: var(--shadow-sm); }
|
||||
.copy-btn .icon-check { display: none; color: var(--color-success); }
|
||||
.copy-btn.is-copied .icon-copy { display: none; }
|
||||
.copy-btn.is-copied .icon-check { display: block; }
|
||||
.copy-toast {
|
||||
position: absolute; bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%);
|
||||
background: #333333; color: var(--color-ink-900); font: var(--text-caption); text-transform: none; letter-spacing: normal;
|
||||
padding: 4px 10px; border-radius: var(--radius-sm); white-space: nowrap;
|
||||
opacity: 0; pointer-events: none; transition: opacity var(--transition-fast);
|
||||
}
|
||||
.copy-btn.is-copied .copy-toast { opacity: 1; }
|
||||
|
||||
.conf-card-footer { display: flex; gap: var(--space-3); margin-top: auto; }
|
||||
.conf-card-footer .btn { flex: 1; justify-content: center; }
|
||||
.icon-btn { width: 40px; height: 40px; flex-shrink: 0; border-radius: 50%; border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-ink-500); display: flex; align-items: center; justify-content: center; }
|
||||
.icon-btn:hover { border-color: var(--color-border-strong); color: var(--color-ink-900); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<svg style="display:none">
|
||||
<symbol id="i-repeat" viewBox="0 0 24 24"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/></symbol>
|
||||
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></symbol>
|
||||
<symbol id="i-check-circle" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||||
<symbol id="i-users" viewBox="0 0 24 24"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></symbol>
|
||||
<symbol id="i-copy" viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></symbol>
|
||||
<symbol id="i-play" viewBox="0 0 24 24"><polygon points="6 3 20 12 6 21 6 3"/></symbol>
|
||||
<symbol id="i-pencil" viewBox="0 0 24 24"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></symbol>
|
||||
<symbol id="i-file-text" viewBox="0 0 24 24"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="16" y2="17"/></symbol>
|
||||
<symbol id="i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></symbol>
|
||||
<symbol id="i-moon" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z"/></symbol>
|
||||
</svg>
|
||||
|
||||
<header class="topbar">
|
||||
<div class="brand-mark"><svg class="logo-mark" viewBox="0 0 32 32" fill="none" aria-hidden="true"><polygon points="21,16 18.5,20.3 13.5,20.3 11,16 13.5,11.7 18.5,11.7" fill="currentColor"/><circle cx="16" cy="16" r="13" stroke="currentColor" stroke-width="2.6" stroke-dasharray="12 6"/></svg> VidConf</div>
|
||||
<nav class="topbar-right">
|
||||
<a href="lobby.html" class="nav-link">Лобби</a>
|
||||
<a href="calendar.html" class="nav-link">Календарь</a>
|
||||
<a href="my-conferences.html" class="nav-link is-active">Мои конференции</a>
|
||||
<a href="admin.html" class="nav-link">Админка</a>
|
||||
<div class="theme-toggle" role="group" aria-label="Переключить тему оформления">
|
||||
<button class="theme-toggle-btn" type="button" aria-pressed="false" aria-label="Светлая тема"><svg class="icon"><use href="#i-sun"/></svg></button>
|
||||
<button class="theme-toggle-btn is-active" type="button" aria-pressed="true" aria-label="Тёмная тема"><svg class="icon"><use href="#i-moon"/></svg></button>
|
||||
</div>
|
||||
<button class="avatar-btn"><span class="avatar">АИ</span><span class="user-name">Анна Иванова</span></button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Мои конференции</h1>
|
||||
<p>5 конференций · 3 закреплённые с повторением</p>
|
||||
</div>
|
||||
<a href="calendar.html" class="btn btn-primary">Запланировать новую</a>
|
||||
</div>
|
||||
|
||||
<div class="filters">
|
||||
<button class="chip is-active">Все</button>
|
||||
<button class="chip"><svg class="icon" style="width:14px;height:14px"><use href="#i-repeat"/></svg>Закреплённые</button>
|
||||
<button class="chip"><svg class="icon" style="width:14px;height:14px"><use href="#i-clock"/></svg>Предстоящие</button>
|
||||
<button class="chip">Завершённые</button>
|
||||
</div>
|
||||
|
||||
<section class="conf-grid">
|
||||
|
||||
<!-- Закреплена, еженедельно -->
|
||||
<article class="conf-card">
|
||||
<div class="conf-card-head">
|
||||
<div class="conf-title-wrap">
|
||||
<h3>Ревью спринта</h3>
|
||||
<p>По вторникам и четвергам, 11:00–11:45</p>
|
||||
</div>
|
||||
<span class="conf-badge pinned"><svg class="icon" style="width:12px;height:12px"><use href="#i-repeat"/></svg>Закреплена</span>
|
||||
</div>
|
||||
<div class="conf-meta-row">
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-clock"/></svg>Ближайшее: вт, 22 июля, 11:00</span>
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-users"/></svg>8 участников</span>
|
||||
</div>
|
||||
<div class="pills-row">
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Ссылка</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">vidconf.ru/j/sprint-review</span>
|
||||
<button class="copy-btn" data-copy="https://vidconf.ru/j/sprint-review" aria-label="Скопировать ссылку">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Номер</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">8842 104</span>
|
||||
<button class="copy-btn" data-copy="8842 104" aria-label="Скопировать номер">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conf-card-footer">
|
||||
<a href="../room.html" class="btn btn-primary"><svg class="icon" style="width:16px;height:16px"><use href="#i-play"/></svg>Войти</a>
|
||||
<button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:17px;height:17px"><use href="#i-pencil"/></svg></button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Закреплена, ежемесячно, закрытая паролем -->
|
||||
<article class="conf-card">
|
||||
<div class="conf-card-head">
|
||||
<div class="conf-title-wrap">
|
||||
<h3><svg class="icon" style="width:15px;height:15px;flex-shrink:0"><use href="#i-lock"/></svg>Синк по релизу 2.4</h3>
|
||||
<p>Раз в месяц, 15-го числа, 14:00–15:00</p>
|
||||
</div>
|
||||
<span class="conf-badge pinned"><svg class="icon" style="width:12px;height:12px"><use href="#i-repeat"/></svg>Закреплена</span>
|
||||
</div>
|
||||
<div class="conf-meta-row">
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-clock"/></svg>Ближайшее: 15 августа, 14:00</span>
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-users"/></svg>4 участника</span>
|
||||
</div>
|
||||
<div class="pills-row">
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Ссылка</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">vidconf.ru/j/release-sync</span>
|
||||
<button class="copy-btn" data-copy="https://vidconf.ru/j/release-sync" aria-label="Скопировать ссылку">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Номер</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">5217 933</span>
|
||||
<button class="copy-btn" data-copy="5217 933" aria-label="Скопировать номер">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conf-card-footer">
|
||||
<a href="join.html" class="btn btn-primary"><svg class="icon" style="width:16px;height:16px"><use href="#i-lock"/></svg>Войти по паролю</a>
|
||||
<button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:17px;height:17px"><use href="#i-pencil"/></svg></button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Предстоящая, разовая -->
|
||||
<article class="conf-card">
|
||||
<div class="conf-card-head">
|
||||
<div class="conf-title-wrap">
|
||||
<h3>1:1 с руководителем</h3>
|
||||
<p>Разовая · 21 июля, 15:00–15:30</p>
|
||||
</div>
|
||||
<span class="conf-badge scheduled"><svg class="icon" style="width:12px;height:12px"><use href="#i-clock"/></svg>Предстоящая</span>
|
||||
</div>
|
||||
<div class="conf-meta-row">
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-users"/></svg>2 участника</span>
|
||||
</div>
|
||||
<div class="pills-row">
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Ссылка</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">vidconf.ru/j/one-on-one</span>
|
||||
<button class="copy-btn" data-copy="https://vidconf.ru/j/one-on-one" aria-label="Скопировать ссылку">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Номер</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">2094 471</span>
|
||||
<button class="copy-btn" data-copy="2094 471" aria-label="Скопировать номер">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conf-card-footer">
|
||||
<button class="btn btn-secondary" style="flex:1" disabled><svg class="icon" style="width:16px;height:16px"><use href="#i-clock"/></svg>Начнётся через 3 дня</button>
|
||||
<button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:17px;height:17px"><use href="#i-pencil"/></svg></button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Завершена (незакреплённая — повторный вход недоступен, только саммари) -->
|
||||
<article class="conf-card is-ended">
|
||||
<div class="conf-card-head">
|
||||
<div class="conf-title-wrap">
|
||||
<h3>Итоги квартала</h3>
|
||||
<p>Разовая · 29 июня, 12:00–13:00</p>
|
||||
</div>
|
||||
<span class="conf-badge ended"><svg class="icon" style="width:12px;height:12px"><use href="#i-check-circle"/></svg>Завершена</span>
|
||||
</div>
|
||||
<div class="conf-meta-row">
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-users"/></svg>14 участников</span>
|
||||
</div>
|
||||
<div class="conf-card-footer">
|
||||
<a href="../email-summary.html" class="btn btn-secondary" style="flex:1"><svg class="icon" style="width:16px;height:16px"><use href="#i-file-text"/></svg>Открыть саммари</a>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Закреплена, каждые N дней -->
|
||||
<article class="conf-card">
|
||||
<div class="conf-card-head">
|
||||
<div class="conf-title-wrap">
|
||||
<h3>Ежеквартальный отчёт</h3>
|
||||
<p>Каждые 90 дней, 10:00–11:00</p>
|
||||
</div>
|
||||
<span class="conf-badge pinned"><svg class="icon" style="width:12px;height:12px"><use href="#i-repeat"/></svg>Закреплена</span>
|
||||
</div>
|
||||
<div class="conf-meta-row">
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-clock"/></svg>Ближайшее: 3 октября, 10:00</span>
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-users"/></svg>22 участника</span>
|
||||
</div>
|
||||
<div class="pills-row">
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Ссылка</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">vidconf.ru/j/q-report</span>
|
||||
<button class="copy-btn" data-copy="https://vidconf.ru/j/q-report" aria-label="Скопировать ссылку">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Номер</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">6630 812</span>
|
||||
<button class="copy-btn" data-copy="6630 812" aria-label="Скопировать номер">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conf-card-footer">
|
||||
<button class="btn btn-secondary" style="flex:1" disabled><svg class="icon" style="width:16px;height:16px"><use href="#i-clock"/></svg>Начнётся 3 октября</button>
|
||||
<button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:17px;height:17px"><use href="#i-pencil"/></svg></button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Копирование ссылки/номера в буфер обмена + временная подсказка «Скопировано».
|
||||
document.querySelectorAll('.copy-btn').forEach((btn) => {
|
||||
const originalLabel = btn.getAttribute('aria-label');
|
||||
btn.addEventListener('click', () => {
|
||||
const text = btn.dataset.copy || '';
|
||||
if (navigator.clipboard) { navigator.clipboard.writeText(text).catch(() => {}); }
|
||||
btn.classList.add('is-copied');
|
||||
btn.setAttribute('aria-label', 'Скопировано');
|
||||
setTimeout(() => {
|
||||
btn.classList.remove('is-copied');
|
||||
btn.setAttribute('aria-label', originalLabel);
|
||||
}, 1500);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
245
design/mockups/email-summary.html
Normal file
245
design/mockups/email-summary.html
Normal file
@@ -0,0 +1,245 @@
|
||||
<!doctype html>
|
||||
<html lang="ru" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Саммари встречи — Спринт-ревью команды платформы</title>
|
||||
<!--[if mso]>
|
||||
<style type="text/css">
|
||||
table { border-collapse: collapse; }
|
||||
.fallback-font { font-family: Arial, sans-serif !important; }
|
||||
</style>
|
||||
<![endif]-->
|
||||
<style>
|
||||
/* Только @media — всё остальное продублировано инлайн для совместимости
|
||||
с почтовыми клиентами (Outlook/Gmail игнорируют <style> в <head>). */
|
||||
@media (max-width: 640px) {
|
||||
.container { width: 100% !important; }
|
||||
.stack { display: block !important; width: 100% !important; }
|
||||
.px-32 { padding-left: 20px !important; padding-right: 20px !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin:0; padding:0; background-color:#F1F1F1; font-family:Helvetica, Arial, sans-serif;">
|
||||
<!-- Preheader (скрытый текст предпросмотра письма) -->
|
||||
<div style="display:none; max-height:0; overflow:hidden; opacity:0;">
|
||||
Саммари встречи «Спринт-ревью команды платформы»: 3 решения, 2 открытых вопроса, запись доступна 30 дней.
|
||||
</div>
|
||||
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:#F1F1F1;">
|
||||
<tr><td align="center" style="padding: 32px 16px;">
|
||||
|
||||
<table role="presentation" class="container" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px; max-width:600px; background-color:#FFFFFF; border-radius:24px; overflow:hidden; border:1px solid #E1E3E9;">
|
||||
|
||||
<!-- ===================== ШАПКА ===================== -->
|
||||
<tr>
|
||||
<td class="px-32" style="padding: 32px 32px 24px 32px; background-color:#F7F7F8;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:14px; font-weight:bold; color:#3B4D95; letter-spacing:.04em;">
|
||||
<!-- Логотип «объектив» (design/logo.svg). SVG — только для HTML-макета:
|
||||
почтовые клиенты (Outlook/большинство Gmail) не рендерят инлайн-SVG.
|
||||
В продакшен-письме заменить растровым PNG (24×24, @2x/@3x для
|
||||
ретины, тот же рисунок, цвет #3B4D95) с атрибутами width/height. -->
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="display:inline-block; vertical-align:middle;"><tr><td style="vertical-align:middle; padding-right:6px;">
|
||||
<svg width="16" height="16" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" style="display:block;">
|
||||
<polygon points="21,16 18.5,20.3 13.5,20.3 11,16 13.5,11.7 18.5,11.7" fill="#3B4D95"/>
|
||||
<circle cx="16" cy="16" r="13" stroke="#3B4D95" stroke-width="2.6" stroke-dasharray="12 6"/>
|
||||
</svg>
|
||||
</td></tr></table><span style="vertical-align:middle;">VIDCONF</span>
|
||||
</td>
|
||||
<td align="right" style="font-family:Helvetica, Arial, sans-serif; font-size:12px; color:#535F94;">
|
||||
Саммари сформировано AI
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="margin: 20px 0 6px; font-family:Helvetica, Arial, sans-serif; font-size:12px; font-weight:bold; letter-spacing:.06em; text-transform:uppercase; color:#6976AC;">
|
||||
Саммари встречи
|
||||
</p>
|
||||
<h1 style="margin:0 0 12px; font-family:Helvetica, Arial, sans-serif; font-size:26px; line-height:1.25; font-weight:bold; color:#2E3454;">
|
||||
Спринт-ревью команды платформы
|
||||
</h1>
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:14px; color:#535F94; padding-right:18px;">
|
||||
📅 15 июля 2026, вт
|
||||
</td>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:14px; color:#535F94; padding-right:18px;">
|
||||
⌛ 45 минут
|
||||
</td>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:14px; color:#535F94;">
|
||||
📍 Переговорная №42
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- ===================== УЧАСТНИКИ ===================== -->
|
||||
<tr>
|
||||
<td class="px-32" style="padding: 24px 32px 8px 32px;">
|
||||
<p style="margin:0 0 12px; font-family:Helvetica, Arial, sans-serif; font-size:12px; font-weight:bold; letter-spacing:.06em; text-transform:uppercase; color:#A6AECB;">
|
||||
Участники (8)
|
||||
</p>
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="padding-right:6px;"><table cellpadding="0" cellspacing="0" border="0"><tr><td width="30" height="30" align="center" valign="middle" bgcolor="#3B4D95" style="border-radius:15px; font-family:Helvetica, Arial, sans-serif; font-size:12px; color:#FFFFFF; font-weight:bold;">ИС</td></tr></table></td>
|
||||
<td style="padding-right:6px;"><table cellpadding="0" cellspacing="0" border="0"><tr><td width="30" height="30" align="center" valign="middle" bgcolor="#535F94" style="border-radius:15px; font-family:Helvetica, Arial, sans-serif; font-size:12px; color:#FFFFFF; font-weight:bold;">МК</td></tr></table></td>
|
||||
<td style="padding-right:6px;"><table cellpadding="0" cellspacing="0" border="0"><tr><td width="30" height="30" align="center" valign="middle" bgcolor="#535F94" style="border-radius:15px; font-family:Helvetica, Arial, sans-serif; font-size:12px; color:#FFFFFF; font-weight:bold;">ВС</td></tr></table></td>
|
||||
<td style="padding-right:6px;"><table cellpadding="0" cellspacing="0" border="0"><tr><td width="30" height="30" align="center" valign="middle" bgcolor="#535F94" style="border-radius:15px; font-family:Helvetica, Arial, sans-serif; font-size:12px; color:#FFFFFF; font-weight:bold;">ОП</td></tr></table></td>
|
||||
<td><table cellpadding="0" cellspacing="0" border="0"><tr><td width="30" height="30" align="center" valign="middle" bgcolor="#F7F7F8" style="border-radius:15px; font-family:Helvetica, Arial, sans-serif; font-size:12px; color:#535F94; font-weight:bold; border:1px solid #E1E3E9;">+4</td></tr></table></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr><td class="px-32" style="padding: 16px 32px;"><hr style="border:none; border-top:1px solid #E1E3E9; margin:0;"></td></tr>
|
||||
|
||||
<!-- ===================== ТЕЗИСЫ ===================== -->
|
||||
<tr>
|
||||
<td class="px-32" style="padding: 8px 32px 24px 32px;">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin-bottom:12px;">
|
||||
<tr>
|
||||
<td width="24" valign="middle">📝</td>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:16px; font-weight:bold; color:#2E3454; padding-left:6px;">Тезисы</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:1.6; color:#2E3454; padding: 6px 0;">
|
||||
• Бэкенд-команда завершила миграцию бронирования на EXCLUDE-констрейнт PostgreSQL, конфликтов брони в проде не зафиксировано за 2 недели.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:1.6; color:#2E3454; padding: 6px 0;">
|
||||
• Транскрайбер на faster-whisper (CPU) укладывается в SLA 10 минут для встреч до часа.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:1.6; color:#2E3454; padding: 6px 0;">
|
||||
• Дизайн-система для комнаты конференции утверждена, фронтенд начинает интеграцию LiveKit на следующей неделе.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- ===================== РЕШЕНИЯ ===================== -->
|
||||
<tr>
|
||||
<td class="px-32" style="padding: 8px 32px 24px 32px; background-color:#E4F7ED;">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin-bottom:12px; padding-top:16px;">
|
||||
<tr>
|
||||
<td width="24" valign="middle" style="color:#0A7A4C;">✅</td>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:16px; font-weight:bold; color:#0A7A4C; padding-left:6px;">Принятые решения</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:1.6; color:#2E3454; padding: 6px 0;">
|
||||
1. Перейти на Qwen2.5-3B Q4_K_M как модель по умолчанию для саммаризации (ответственный: Виктор Седых, до 22.07).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:1.6; color:#2E3454; padding: 6px 0;">
|
||||
2. Хранить пароли комнат только в виде argon2-хэшей, ротацию делать через админку (ответственный: Ольга Панина).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:1.6; color:#2E3454; padding: 6px 0;">
|
||||
3. Запись конференций хранить 30 дней, затем удалять по расписанию Celery beat.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- ===================== ОТКРЫТЫЕ ВОПРОСЫ ===================== -->
|
||||
<tr>
|
||||
<td class="px-32" style="padding: 24px 32px; background-color:#FBF0DC;">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin-bottom:12px;">
|
||||
<tr>
|
||||
<td width="24" valign="middle" style="color:#7F560D;">❓</td>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:16px; font-weight:bold; color:#7F560D; padding-left:6px;">Открытые вопросы</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:1.6; color:#2E3454; padding: 6px 0;">
|
||||
• Кто согласует лимиты Celery worker concurrency для транскрайбера на проде — DevOps или бэкенд-лид?
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:1.6; color:#2E3454; padding: 6px 0;">
|
||||
• Нужна ли поддержка GPU-инференса как отдельный плагин Transcriber уже в Фазе 3, или отложить до масштабирования?
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- ===================== ЦИФРЫ И ФАКТЫ ===================== -->
|
||||
<tr>
|
||||
<td class="px-32" style="padding: 24px 32px;">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin-bottom:16px;">
|
||||
<tr>
|
||||
<td width="24" valign="middle">📊</td>
|
||||
<td style="font-family:Helvetica, Arial, sans-serif; font-size:16px; font-weight:bold; color:#2E3454; padding-left:6px;">Цифры и факты</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border:1px solid #E1E3E9; border-radius:12px;">
|
||||
<tr>
|
||||
<td width="33%" align="center" style="padding:18px 8px; border-right:1px solid #E1E3E9;">
|
||||
<div style="font-family:Helvetica, Arial, sans-serif; font-size:28px; font-weight:bold; color:#3B4D95;">45 мин</div>
|
||||
<div style="font-family:Helvetica, Arial, sans-serif; font-size:12px; color:#535F94; margin-top:4px;">длительность</div>
|
||||
</td>
|
||||
<td width="33%" align="center" style="padding:18px 8px; border-right:1px solid #E1E3E9;">
|
||||
<div style="font-family:Helvetica, Arial, sans-serif; font-size:28px; font-weight:bold; color:#3B4D95;">3</div>
|
||||
<div style="font-family:Helvetica, Arial, sans-serif; font-size:12px; color:#535F94; margin-top:4px;">решения</div>
|
||||
</td>
|
||||
<td width="34%" align="center" style="padding:18px 8px;">
|
||||
<div style="font-family:Helvetica, Arial, sans-serif; font-size:28px; font-weight:bold; color:#3B4D95;">8</div>
|
||||
<div style="font-family:Helvetica, Arial, sans-serif; font-size:12px; color:#535F94; margin-top:4px;">участников</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- ===================== CTA ===================== -->
|
||||
<tr>
|
||||
<td class="px-32" align="center" style="padding: 8px 32px 32px 32px;">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||
<tr>
|
||||
<!-- #D4F2E3 --color-accent (осветлено на 50% от #A9E4C6, фаза R1 итерация) -->
|
||||
<td align="center" bgcolor="#D4F2E3" style="border-radius:999px;">
|
||||
<!-- #2E3454 --color-accent-text -->
|
||||
<a href="#" style="display:block; padding:14px 28px; font-family:Helvetica, Arial, sans-serif; font-size:15px; font-weight:bold; color:#2E3454; text-decoration:none;">
|
||||
Смотреть полную расшифровку →
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- ===================== ФУТЕР ===================== -->
|
||||
<tr>
|
||||
<td class="px-32" style="padding: 20px 32px 32px 32px; background-color:#F7F7F8; border-top:1px solid #E1E3E9;">
|
||||
<p style="margin:0 0 8px; font-family:Helvetica, Arial, sans-serif; font-size:12px; line-height:1.6; color:#A6AECB;">
|
||||
Письмо сформировано автоматически по итогам конференции в VidConf (self-hosted). Запись и полная расшифровка доступны 30 дней.
|
||||
</p>
|
||||
<p style="margin:0; font-family:Helvetica, Arial, sans-serif; font-size:12px; color:#A6AECB;">
|
||||
<a href="#" style="color:#535F94; text-decoration:underline;">Настройки уведомлений</a>
|
||||
·
|
||||
<a href="#" style="color:#535F94; text-decoration:underline;">Отписаться от саммари</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
193
design/mockups/join.html
Normal file
193
design/mockups/join.html
Normal file
@@ -0,0 +1,193 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>VidConf — Подключение к конференции</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../tokens.css">
|
||||
<style>
|
||||
/*
|
||||
* Подключение к конференции (фаза R1, пивот) — три состояния одного flow:
|
||||
* (а) ввод ссылки/номера, (б) «представиться» для гостя (имя обязательно,
|
||||
* email факультативно — для саммари), (в) пароль закрытой конференции.
|
||||
* Вход по ссылке/номеру доступен любому, включая гостей без аккаунта —
|
||||
* поэтому экран не требует авторизации и не показывает брендовую
|
||||
* sales-панель auth.html, только компактный центрированный поток.
|
||||
*/
|
||||
|
||||
html { min-width: 1280px; }
|
||||
body { min-height: 100vh; display: flex; flex-direction: column; background: var(--color-bg); }
|
||||
|
||||
.topbar { display: flex; align-items: center; padding: var(--space-5) var(--space-8); }
|
||||
.brand-mark { display: inline-flex; align-items: center; gap: 8px; font: var(--text-h3); color: var(--color-ink-700); text-decoration: none; }
|
||||
.brand-mark .logo-mark { width: 22px; height: 22px; flex-shrink: 0; }
|
||||
|
||||
main { flex: 1; display: flex; flex-direction: column; align-items: center; padding: var(--space-6) var(--space-8) var(--space-16); gap: var(--space-16); }
|
||||
|
||||
.screen-block { width: 100%; max-width: 440px; }
|
||||
.screen-label { font: var(--text-caption); color: var(--color-ink-400); margin: 0 0 var(--space-3); text-align: center; }
|
||||
.screen-label b { color: var(--color-ink-700); }
|
||||
|
||||
.flow-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); padding: var(--space-8); }
|
||||
.flow-icon { width: 52px; height: 52px; border-radius: var(--radius-full); background: var(--color-bg-alt); color: var(--color-ink-700); display: flex; align-items: center; justify-content: center; margin: 0 auto var(--space-5); }
|
||||
.flow-icon .icon { width: 24px; height: 24px; }
|
||||
.flow-card h1 { font: var(--text-h1); color: var(--color-ink-900); margin: 0 0 var(--space-2); text-align: center; }
|
||||
.flow-card .flow-sub { font: var(--text-body-lg); color: var(--color-ink-500); margin: 0 0 var(--space-6); text-align: center; }
|
||||
|
||||
.field { margin-bottom: var(--space-5); }
|
||||
.field label { display: flex; align-items: center; gap: 6px; font: var(--text-body); font-weight: 600; color: var(--color-ink-900); margin-bottom: var(--space-2); }
|
||||
.field-optional { font-weight: 400; color: var(--color-ink-400); }
|
||||
.input-wrap { position: relative; }
|
||||
.input-wrap .icon { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--color-ink-400); }
|
||||
.input-wrap input {
|
||||
width: 100%; font: var(--text-body-lg); color: var(--color-ink-900);
|
||||
padding: 11px 14px 11px 42px; border-radius: var(--radius-md); border: 1px solid var(--color-border);
|
||||
background: var(--color-surface); outline: none;
|
||||
}
|
||||
.input-wrap input::placeholder { color: var(--color-ink-300); }
|
||||
.input-wrap input:hover { border-color: var(--color-border-strong); }
|
||||
.input-wrap input:focus { border-color: var(--color-focus-ring); box-shadow: 0 0 0 3px rgba(59,77,149,.12); }
|
||||
.input-wrap .toggle-eye { left: auto; right: 14px; background: none; border: none; padding: 0; cursor: pointer; }
|
||||
.field-hint { margin-top: var(--space-2); font: var(--text-body); color: var(--color-ink-500); }
|
||||
.field.has-error input { border-color: var(--color-danger); box-shadow: 0 0 0 3px rgba(199,54,79,.1); }
|
||||
.error-msg { display: flex; align-items: center; gap: 6px; margin-top: var(--space-2); font: var(--text-body); color: var(--color-danger); }
|
||||
|
||||
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; width: 100%; border: none; cursor: pointer; font-family: var(--font-body); transition: background var(--transition-fast), box-shadow var(--transition-fast); }
|
||||
.btn-primary { background: var(--color-accent); color: var(--color-accent-text); font: var(--text-body-lg); font-weight: 700; padding: 13px 24px; border-radius: var(--radius-full); }
|
||||
.btn-primary:hover { background: var(--color-accent-hover); box-shadow: var(--shadow-md); }
|
||||
.btn-primary:active { background: var(--color-accent-active); }
|
||||
.btn-primary[disabled] { background: rgba(166,174,203,.4); color: var(--color-surface); cursor: not-allowed; }
|
||||
.btn-secondary { background: transparent; color: var(--color-ink-700); border: 1.5px solid var(--color-border-pill); font: var(--text-body-lg); font-weight: 600; padding: 12px 24px; border-radius: var(--radius-full); }
|
||||
.btn-secondary:hover { background: var(--color-bg-alt); }
|
||||
:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
|
||||
|
||||
.link-back { display: block; text-align: center; margin-top: var(--space-5); font: var(--text-body); font-weight: 600; color: var(--color-ink-500); text-decoration: none; }
|
||||
.link-back:hover { color: var(--color-ink-700); }
|
||||
|
||||
/* Плашка целевой конференции (состояния б/в) */
|
||||
.target-conf { display: flex; align-items: center; gap: 12px; background: var(--color-bg-alt); border-radius: var(--radius-lg); padding: var(--space-4); margin-bottom: var(--space-6); }
|
||||
.target-conf .icon-wrap { width: 40px; height: 40px; border-radius: 50%; background: var(--color-surface); border: 1px solid var(--color-border); color: var(--color-ink-700); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.target-conf strong { display: block; font: var(--text-body); color: var(--color-ink-900); }
|
||||
.target-conf span { font: var(--text-mono-sm); color: var(--color-ink-500); }
|
||||
|
||||
.divider { border: none; border-top: 1px solid var(--color-border); width: 100%; max-width: 720px; margin: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<svg style="display:none">
|
||||
<symbol id="i-link2" viewBox="0 0 24 24"><path d="M9 17H7A5 5 0 0 1 7 7h2"/><path d="M15 7h2a5 5 0 1 1 0 10h-2"/><line x1="8" y1="12" x2="16" y2="12"/></symbol>
|
||||
<symbol id="i-hash" viewBox="0 0 24 24"><line x1="4" y1="9" x2="20" y2="9"/><line x1="4" y1="15" x2="20" y2="15"/><line x1="10" y1="3" x2="8" y2="21"/><line x1="16" y1="3" x2="14" y2="21"/></symbol>
|
||||
<symbol id="i-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M6 21v-2a6 6 0 0 1 12 0v2"/></symbol>
|
||||
<symbol id="i-mail" viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m2 6 10 7 10-7"/></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||||
<symbol id="i-eye" viewBox="0 0 24 24"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></symbol>
|
||||
<symbol id="i-eye-off" viewBox="0 0 24 24"><path d="M3 3l18 18"/><path d="M10.58 10.58a2 2 0 0 0 2.83 2.83"/><path d="M9.88 5.09A9.77 9.77 0 0 1 12 5c6.5 0 10 7 10 7a13.16 13.16 0 0 1-3.06 4.06M6.1 6.1A13.16 13.16 0 0 0 2 12s3.5 7 10 7a9.77 9.77 0 0 0 4.9-1.3"/></symbol>
|
||||
<symbol id="i-arrow-right" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></symbol>
|
||||
<symbol id="i-alert" viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></symbol>
|
||||
<symbol id="i-video" viewBox="0 0 24 24"><path d="M15 6H4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2v-2.5l5 3.5V7l-5 3.5V8a2 2 0 0 0-2-2z"/></symbol>
|
||||
</svg>
|
||||
|
||||
<header class="topbar">
|
||||
<a class="brand-mark" href="lobby.html"><svg class="logo-mark" viewBox="0 0 32 32" fill="none" aria-hidden="true"><polygon points="21,16 18.5,20.3 13.5,20.3 11,16 13.5,11.7 18.5,11.7" fill="currentColor"/><circle cx="16" cy="16" r="13" stroke="currentColor" stroke-width="2.6" stroke-dasharray="12 6"/></svg> VidConf</a>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<!-- ===================== (а) ВВОД ССЫЛКИ ИЛИ НОМЕРА ===================== -->
|
||||
<section class="screen-block">
|
||||
<p class="screen-label">Экран: <b>ввод ссылки или номера</b></p>
|
||||
<div class="flow-card">
|
||||
<div class="flow-icon" aria-hidden="true"><svg class="icon"><use href="#i-link2"/></svg></div>
|
||||
<h1>Подключиться к конференции</h1>
|
||||
<p class="flow-sub">Вставьте ссылку-приглашение или введите номер конференции</p>
|
||||
|
||||
<form>
|
||||
<div class="field">
|
||||
<label for="join-input">Ссылка или номер</label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-hash"/></svg>
|
||||
<input id="join-input" type="text" placeholder="vidconf.ru/j/... или 8842 104" value="8842 104">
|
||||
</div>
|
||||
<p class="field-hint">Найдите номер или ссылку в приглашении организатора</p>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Продолжить <svg class="icon"><use href="#i-arrow-right"/></svg></button>
|
||||
</form>
|
||||
<a href="lobby.html" class="link-back">Назад в лобби</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<!-- ===================== (б) ПРЕДСТАВИТЬСЯ (ГОСТЬ) ===================== -->
|
||||
<section class="screen-block">
|
||||
<p class="screen-label">Экран: <b>представиться (гость)</b></p>
|
||||
<div class="flow-card">
|
||||
<div class="target-conf">
|
||||
<span class="icon-wrap" aria-hidden="true"><svg class="icon" style="width:18px;height:18px"><use href="#i-video"/></svg></span>
|
||||
<div><strong>Синк по релизу 2.4</strong><span>№ 8842 104</span></div>
|
||||
</div>
|
||||
|
||||
<div class="flow-icon" aria-hidden="true"><svg class="icon"><use href="#i-user"/></svg></div>
|
||||
<h1>Как вас зовут?</h1>
|
||||
<p class="flow-sub">У вас нет аккаунта VidConf — представьтесь, чтобы войти как гость</p>
|
||||
|
||||
<form>
|
||||
<div class="field">
|
||||
<label for="guest-name">Имя<span class="field-optional">· обязательно</span></label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-user"/></svg>
|
||||
<input id="guest-name" type="text" placeholder="Как к вам обращаться" value="Пётр Волков">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="guest-email">Email<span class="field-optional">· необязательно</span></label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-mail"/></svg>
|
||||
<input id="guest-email" type="email" placeholder="you@mail.ru">
|
||||
</div>
|
||||
<p class="field-hint">Укажите, если хотите получить саммари конференции на почту</p>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Войти в конференцию <svg class="icon"><use href="#i-arrow-right"/></svg></button>
|
||||
</form>
|
||||
<a href="join.html" class="link-back">Назад</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<!-- ===================== (в) ПАРОЛЬ ЗАКРЫТОЙ КОНФЕРЕНЦИИ ===================== -->
|
||||
<section class="screen-block">
|
||||
<p class="screen-label">Экран: <b>пароль закрытой конференции</b> (с состоянием ошибки)</p>
|
||||
<div class="flow-card">
|
||||
<div class="target-conf">
|
||||
<span class="icon-wrap" aria-hidden="true"><svg class="icon" style="width:18px;height:18px"><use href="#i-lock"/></svg></span>
|
||||
<div><strong>Итоги квартала</strong><span>№ 4471 208 · закрытая</span></div>
|
||||
</div>
|
||||
|
||||
<div class="flow-icon" aria-hidden="true"><svg class="icon"><use href="#i-lock"/></svg></div>
|
||||
<h1>Введите пароль</h1>
|
||||
<p class="flow-sub">Эта конференция закрыта — доступ только по паролю от организатора</p>
|
||||
|
||||
<form>
|
||||
<div class="field has-error">
|
||||
<label for="join-pass">Пароль конференции</label>
|
||||
<div class="input-wrap">
|
||||
<svg class="icon"><use href="#i-lock"/></svg>
|
||||
<input id="join-pass" type="password" placeholder="••••••••" value="wrongpass">
|
||||
<button type="button" class="toggle-eye" aria-label="Показать пароль">
|
||||
<svg class="icon"><use href="#i-eye"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<p class="error-msg"><svg class="icon" style="width:16px;height:16px"><use href="#i-alert"/></svg> Неверный пароль, попробуйте ещё раз</p>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Войти <svg class="icon"><use href="#i-arrow-right"/></svg></button>
|
||||
</form>
|
||||
<a href="join.html" class="link-back">Назад</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
414
design/mockups/lobby.html
Normal file
414
design/mockups/lobby.html
Normal file
@@ -0,0 +1,414 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>VidConf — Лобби (хаб действий)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../tokens.css">
|
||||
<style>
|
||||
/*
|
||||
* Лобби — «хаб действий» (фаза R1, пивот). Заменяет список комнат:
|
||||
* предустановленных комнат больше нет, конференции создаются динамически.
|
||||
* По мотивам alternative_design/lobby.html, с правками фазы R1:
|
||||
* - 4 плитки СТРОГО одинакового размера (фиксированная высота, не min-height,
|
||||
* чтобы сетка была симметричной вне зависимости от длины текста внутри);
|
||||
* - симметричная сетка 2×2 без grid-template-areas (порядок в DOM = порядок
|
||||
* чтения = порядок задачи: Создать → Календарь → Подключиться → Мои);
|
||||
* - плитки крупнее референсного эскиза (высота 340px против ~220px в alt);
|
||||
* - медальон VidConf на пересечении рядов/колонок (без магических % как
|
||||
* в alt-версии; точную формулу положения см. в правке 2-й волны ниже —
|
||||
* после неё центр уже не буквально 50%/50%, а зеркально-симметричная
|
||||
* точка равного перекрытия всех 4 карточек).
|
||||
* Токены — только новые имена (--color-accent*), см. tokens.css §1.1/1.3.
|
||||
*
|
||||
* Итерация правок (2026-07-16, гейт утверждения, 1-я волна): плитки
|
||||
* расширены в 1.5 раза (тайл 544px → 816px, контейнер 1120px → 1664px,
|
||||
* gap не менялся — ширина реально выросла, а не растянулась через gap);
|
||||
* типографика внутри плиток укрупнена пропорционально (см.
|
||||
* .action-icon/.action-title/.action-object/.action-note/.hub-logo ниже).
|
||||
* Высота ряда (340px) сохранена без изменений — просили шире, не крупнее
|
||||
* по высоте.
|
||||
*
|
||||
* Итерация правок (2026-07-16, гейт утверждения, 2-я волна): медальон
|
||||
* заменён на знак-объектив (design/logo.svg, см. DESIGN_SYSTEM.md §1.4) и
|
||||
* теперь ЛЕЖИТ ПОВЕРХ равных частей видимых углов всех 4 карточек (было —
|
||||
* просто сидел в промежутке сетки на пересечении 50%/50%). Точная
|
||||
* геометрия и обоснование смещения центра медальона — в комментарии перед
|
||||
* `.hub-logo` и в DESIGN_SYSTEM.md §4.12.
|
||||
*
|
||||
* Итерация правок (2026-07-16, гейт утверждения, 3-я волна): плитки сужены
|
||||
* на 15% (тайл 816px → 694px, контейнер 1664px → 1420px); высота (340px)
|
||||
* и gap (32px) не менялись, типографика не менялась. Перекрытие медальона
|
||||
* зависит только от gap/margin-top (не от ширины тайла) — геометрия и
|
||||
* величина перекрытия (29.2px) остаются теми же, см. `.hub-logo` ниже;
|
||||
* пересчёт подтверждён headless-рендером на 1280/1440/1728px.
|
||||
*/
|
||||
|
||||
html { height: 100%; min-width: 1280px; }
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background:
|
||||
radial-gradient(1100px 520px at 10% -10%, rgba(59, 77, 149, 0.06), transparent 60%),
|
||||
radial-gradient(900px 480px at 105% 110%, rgba(212, 242, 227, 0.28), transparent 55%),
|
||||
var(--color-bg);
|
||||
}
|
||||
|
||||
/* ---------- Верхняя панель ---------- */
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-5) var(--space-8);
|
||||
}
|
||||
|
||||
.brand-mark { display: inline-flex; align-items: center; gap: 8px; font: var(--text-h3); color: var(--color-ink-700); }
|
||||
.brand-mark .logo-mark { width: 22px; height: 22px; flex-shrink: 0; }
|
||||
|
||||
.topbar-right { display: flex; align-items: center; gap: var(--space-4); }
|
||||
|
||||
.burger {
|
||||
width: 44px; height: 44px;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-ink-700);
|
||||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
|
||||
}
|
||||
.burger:hover { border-color: var(--color-border-strong); box-shadow: var(--shadow-sm); }
|
||||
.burger[aria-expanded="true"] { border-color: var(--color-ink-700); }
|
||||
|
||||
.menu-wrap { position: relative; }
|
||||
.menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 10px);
|
||||
right: 0;
|
||||
min-width: 236px;
|
||||
padding: var(--space-2);
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-lg);
|
||||
display: none;
|
||||
z-index: 30;
|
||||
}
|
||||
.menu.is-open { display: block; }
|
||||
.menu a {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 10px 14px;
|
||||
border-radius: var(--radius-sm);
|
||||
font: var(--text-body-lg); font-weight: 600;
|
||||
color: var(--color-ink-900);
|
||||
text-decoration: none;
|
||||
}
|
||||
.menu a:hover { background: var(--color-bg-alt); color: var(--color-ink-700); }
|
||||
.menu a .icon { color: var(--color-ink-400); }
|
||||
.menu-divider { height: 1px; background: var(--color-border); margin: var(--space-2) 6px; }
|
||||
.menu a.danger { color: var(--color-danger); }
|
||||
.menu a.danger .icon { color: var(--color-danger); }
|
||||
|
||||
.user-pill {
|
||||
display: inline-flex; align-items: center; gap: 10px;
|
||||
padding: 6px 16px 6px 6px;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-full);
|
||||
font: var(--text-body); font-weight: 600; color: var(--color-ink-900);
|
||||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
|
||||
}
|
||||
.user-pill:hover { border-color: var(--color-border-strong); box-shadow: var(--shadow-sm); }
|
||||
.user-pill .avatar {
|
||||
width: 30px; height: 30px; border-radius: 50%;
|
||||
background: var(--color-ink-700); color: #fff;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font: var(--text-caption);
|
||||
}
|
||||
|
||||
/* ---------- Мозаика действий ---------- */
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-6) var(--space-8) var(--space-12);
|
||||
}
|
||||
|
||||
.hub-wrap { display: flex; flex-direction: column; align-items: center; gap: var(--space-8); width: 100%; }
|
||||
/* width:100% — обязателен: без явной ширины .hub-wrap как flex-item без
|
||||
flex-grow сжимается по контенту (max-content), и width: min(…, 100%)
|
||||
у .hub не может резолвиться от неопределённого контейнера (циклическая
|
||||
зависимость) — контейнер сетки фактически игнорировал заданный кап.
|
||||
С width:100% контейнер .hub-wrap получает определённую ширину от
|
||||
<main>, и .hub реально расширяется до заданного max-width. */
|
||||
.hub-intro { text-align: center; max-width: 560px; }
|
||||
.hub-intro .eyebrow { font: var(--text-caption); color: var(--color-ink-400); margin: 0 0 var(--space-2); }
|
||||
.hub-intro h1 { font: var(--text-display-lg); font-family: var(--font-display); color: var(--color-ink-700); margin: 0; }
|
||||
|
||||
.hub {
|
||||
position: relative;
|
||||
width: min(1420px, 100%); /* тайл 694px × 2 + gap 32px. Правка гейта
|
||||
(2026-07-16, 3-я итерация): тайл ×0.85 от предыдущих 816px (816×0.85=
|
||||
693.6≈694px, контейнер 1664px→1420px); высота (340px) и gap (32px) не
|
||||
менялись — сужение только по горизонтали. */
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-rows: repeat(2, 340px); /* фиксированная высота — гарантирует
|
||||
строгую симметрию сетки 2×2 независимо от длины текста в плитках */
|
||||
gap: var(--space-8);
|
||||
}
|
||||
|
||||
/* Карточка-действие. Медальон с иконкой «сидит» на верхней кромке. */
|
||||
.action {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-10) var(--space-12) var(--space-8);
|
||||
margin-top: 34px; /* место под выступающий медальон (68px / 2) */
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-3xl);
|
||||
text-decoration: none;
|
||||
color: var(--color-ink-900);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition:
|
||||
transform var(--transition-base),
|
||||
box-shadow var(--transition-base),
|
||||
border-color var(--transition-base);
|
||||
}
|
||||
.action:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
border-color: var(--color-border-strong);
|
||||
}
|
||||
.action:active { transform: translateY(-1px); }
|
||||
|
||||
.action-icon {
|
||||
position: absolute;
|
||||
top: -34px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 68px; height: 68px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-surface);
|
||||
border: var(--border-thick) solid var(--color-ink-700);
|
||||
color: var(--color-ink-700);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
.action-icon .icon { width: 30px; height: 30px; }
|
||||
|
||||
/* Типографика плиток укрупнена пропорционально расширению тайла (итерация
|
||||
правок гейта, 2026-07-16): заголовок 34px → 40px, подпись-объект
|
||||
18px → 21px, примечание 16px → 17px. Токены шкалы (--text-display-lg
|
||||
и т.д.) не подходят напрямую — берём кастомные размеры того же
|
||||
начертания/семейства для гармоничного шага. */
|
||||
.action-title {
|
||||
margin: 0;
|
||||
font: 600 40px/1.2 var(--font-display);
|
||||
letter-spacing: 0.01em;
|
||||
color: var(--color-ink-700);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.action-object { margin: 0; font: 600 21px/1.35 var(--font-body); color: var(--color-ink-900); }
|
||||
.action-note { margin: 0; font: 400 17px/1.5 var(--font-body); color: var(--color-ink-500); }
|
||||
|
||||
/* Главное действие — CTA дизайн-системы (пастельная мята) */
|
||||
.action--create {
|
||||
background: var(--color-accent);
|
||||
border-color: transparent;
|
||||
}
|
||||
.action--create:hover { background: var(--color-accent-hover); border-color: transparent; }
|
||||
.action--create:active { background: var(--color-accent-active); }
|
||||
.action--create .action-title { font: 700 54px/1.1 var(--font-display); }
|
||||
.action--create .action-title,
|
||||
.action--create .action-object { color: var(--color-accent-text); }
|
||||
.action--create .action-note { color: var(--color-ink-700); }
|
||||
.action--create .action-icon {
|
||||
background: var(--color-accent);
|
||||
border-color: var(--color-accent-text);
|
||||
color: var(--color-accent-text);
|
||||
}
|
||||
|
||||
/* ---------- Медальон VidConf в центре мозаики ---------- */
|
||||
/* Правка гейта (2026-07-16, вторая итерация): медальон должен ЛЕЖАТЬ ПОВЕРХ
|
||||
видимых углов всех 4 карточек на одинаковую величину, а не просто сидеть
|
||||
в промежутке сетки. Геометрия (числа фиксированные, не проценты — высота
|
||||
.hub всегда 2×340px + gap 32px = 712px, независимо от ширины вьюпорта):
|
||||
• по вертикали видимый низ карточек ряда 1 — на y=340px от верха .hub
|
||||
(340px трек), видимый верх карточек ряда 2 — на y=406px (грид-гап 32px
|
||||
+ margin-top 34px карточки под собственный action-icon). Равноудалённая
|
||||
от обеих точка — y=373px (33px до каждой), а не геометрический центр
|
||||
сетки 356px (712/2) — из-за несимметричного резерва под action-icon.
|
||||
• по горизонтали — строго центр колонок (gap 32px, по 16px до каждой
|
||||
карточки), там асимметрии нет.
|
||||
Медальон центрирован в (50%, 373px). Такое позиционирование даёт ТОЧНО
|
||||
одинаковое перекрытие всех 4 углов по построению (зеркальная симметрия
|
||||
по обеим осям), проверено headless-рендером: offset между углами — 0px
|
||||
(см. design/tools/ — рендер-проверка выполнялась вручную скриптом, не
|
||||
хранится в репозитории).
|
||||
Диаметр 156px (было 132px) при радиусе скругления карточек 32px
|
||||
(--radius-3xl) даёт перекрытие видимой (закруглённой) части каждой
|
||||
карточки ≈29px по диагонали — в середине требуемого диапазона 20–35px. */
|
||||
.hub-logo {
|
||||
position: absolute;
|
||||
top: 373px; left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 156px; height: 156px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-surface);
|
||||
border: var(--border-thick) solid var(--color-ink-700);
|
||||
box-shadow: var(--shadow-lg);
|
||||
display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center;
|
||||
gap: 6px;
|
||||
z-index: 20;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hub-logo .logo-mark {
|
||||
width: 56px; height: 56px;
|
||||
color: var(--color-ink-700);
|
||||
}
|
||||
.hub-logo .logo-word {
|
||||
font: 600 16px/1 var(--font-display);
|
||||
color: var(--color-ink-700);
|
||||
}
|
||||
|
||||
/* ---------- Появление ---------- */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.action, .hub-logo {
|
||||
animation: rise 480ms cubic-bezier(0.22, 0.9, 0.3, 1) backwards;
|
||||
}
|
||||
.action--create { animation-delay: 60ms; }
|
||||
.action--calendar { animation-delay: 140ms; }
|
||||
.action--join { animation-delay: 220ms; }
|
||||
.action--mine { animation-delay: 300ms; }
|
||||
.hub-logo { animation-delay: 420ms; }
|
||||
@keyframes rise {
|
||||
from { opacity: 0; transform: translateY(14px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.hub-logo { animation-name: rise-logo; }
|
||||
@keyframes rise-logo {
|
||||
from { opacity: 0; transform: translate(-50%, calc(-50% + 14px)); }
|
||||
to { opacity: 1; transform: translate(-50%, -50%); }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<svg style="display:none" aria-hidden="true">
|
||||
<symbol id="i-video" viewBox="0 0 24 24"><path d="M15 6H4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2v-2.5l5 3.5V7l-5 3.5V8a2 2 0 0 0-2-2z"/></symbol>
|
||||
<symbol id="i-calendar" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></symbol>
|
||||
<symbol id="i-link2" viewBox="0 0 24 24"><path d="M9 17H7A5 5 0 0 1 7 7h2"/><path d="M15 7h2a5 5 0 1 1 0 10h-2"/><line x1="8" y1="12" x2="16" y2="12"/></symbol>
|
||||
<symbol id="i-user" viewBox="0 0 24 24"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></symbol>
|
||||
<symbol id="i-download" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></symbol>
|
||||
<symbol id="i-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></symbol>
|
||||
<symbol id="i-layout-dashboard" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></symbol>
|
||||
<symbol id="i-logout" viewBox="0 0 24 24"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></symbol>
|
||||
</svg>
|
||||
|
||||
<header class="topbar">
|
||||
<div class="brand-mark"><svg class="logo-mark" viewBox="0 0 32 32" fill="none" aria-hidden="true"><polygon points="21,16 18.5,20.3 13.5,20.3 11,16 13.5,11.7 18.5,11.7" fill="currentColor"/><circle cx="16" cy="16" r="13" stroke="currentColor" stroke-width="2.6" stroke-dasharray="12 6"/></svg> VidConf</div>
|
||||
|
||||
<div class="topbar-right">
|
||||
<button class="user-pill">
|
||||
<span class="avatar" aria-hidden="true">ИИ</span>
|
||||
Иванов Иван
|
||||
</button>
|
||||
<div class="menu-wrap">
|
||||
<button class="burger" id="burger" aria-expanded="false" aria-controls="main-menu" aria-label="Открыть меню">
|
||||
<svg class="icon" viewBox="0 0 24 24"><line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="17" x2="20" y2="17"/></svg>
|
||||
</button>
|
||||
<nav class="menu" id="main-menu" aria-label="Основное меню">
|
||||
<a href="#"><svg class="icon"><use href="#i-user"/></svg> Профиль</a>
|
||||
<a href="admin.html"><svg class="icon"><use href="#i-layout-dashboard"/></svg> Админка</a>
|
||||
<a href="#"><svg class="icon"><use href="#i-download"/></svg> Скачать приложение</a>
|
||||
<a href="#"><svg class="icon"><use href="#i-info"/></svg> О сервисе</a>
|
||||
<div class="menu-divider"></div>
|
||||
<a href="auth.html" class="danger"><svg class="icon"><use href="#i-logout"/></svg> Выйти</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="hub-wrap">
|
||||
<div class="hub-intro">
|
||||
<p class="eyebrow">[ ДОБРО ПОЖАЛОВАТЬ В VIDCONF ]</p>
|
||||
<h1>Что делаем сегодня?</h1>
|
||||
</div>
|
||||
|
||||
<div class="hub">
|
||||
|
||||
<a class="action action--create" href="room.html">
|
||||
<span class="action-icon" aria-hidden="true"><svg class="icon"><use href="#i-video"/></svg></span>
|
||||
<h2 class="action-title">Создать</h2>
|
||||
<p class="action-object">конференцию</p>
|
||||
<p class="action-note">начнётся сразу — вы первый участник</p>
|
||||
</a>
|
||||
|
||||
<a class="action action--calendar" href="calendar.html">
|
||||
<span class="action-icon" aria-hidden="true"><svg class="icon"><use href="#i-calendar"/></svg></span>
|
||||
<h2 class="action-title">Календарь</h2>
|
||||
<p class="action-object">конференций</p>
|
||||
<p class="action-note">запланировать · закрепить · повторять</p>
|
||||
</a>
|
||||
|
||||
<a class="action action--join" href="join.html">
|
||||
<span class="action-icon" aria-hidden="true"><svg class="icon"><use href="#i-link2"/></svg></span>
|
||||
<h2 class="action-title">Подключиться</h2>
|
||||
<p class="action-object">к конференции</p>
|
||||
<p class="action-note">по ссылке или номеру</p>
|
||||
</a>
|
||||
|
||||
<a class="action action--mine" href="my-conferences.html">
|
||||
<span class="action-icon" aria-hidden="true"><svg class="icon"><use href="#i-user"/></svg></span>
|
||||
<h2 class="action-title">Мои</h2>
|
||||
<p class="action-object">конференции</p>
|
||||
<p class="action-note">закреплённые и предстоящие</p>
|
||||
</a>
|
||||
|
||||
<div class="hub-logo" aria-hidden="true">
|
||||
<svg class="logo-mark" viewBox="0 0 64 64" fill="none" aria-hidden="true">
|
||||
<polygon points="41,32 36.5,39.8 27.5,39.8 23,32 27.5,24.2 36.5,24.2" fill="currentColor"/>
|
||||
<circle cx="32" cy="32" r="15" stroke="currentColor" stroke-width="3" stroke-dasharray="16 7 28 9 12 16" stroke-dashoffset="5"/>
|
||||
<circle cx="32" cy="32" r="20" stroke="currentColor" stroke-width="2" stroke-dasharray="22 10 8 14 34 12 6 10" stroke-dashoffset="40"/>
|
||||
<circle cx="32" cy="32" r="25" stroke="currentColor" stroke-width="3.5" stroke-dasharray="40 14 12 10 26 16 20 8" stroke-dashoffset="80"/>
|
||||
<circle cx="32" cy="32" r="29" stroke="currentColor" stroke-width="1.6" stroke-dasharray="10 6 60 20 30 10 24 8" stroke-dashoffset="120"/>
|
||||
</svg>
|
||||
<span class="logo-word">VidConf</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Бургер-меню: открытие/закрытие, клик мимо и Escape закрывают.
|
||||
const burger = document.getElementById('burger');
|
||||
const menu = document.getElementById('main-menu');
|
||||
|
||||
function setMenu(open) {
|
||||
menu.classList.toggle('is-open', open);
|
||||
burger.setAttribute('aria-expanded', String(open));
|
||||
burger.setAttribute('aria-label', open ? 'Закрыть меню' : 'Открыть меню');
|
||||
}
|
||||
|
||||
burger.addEventListener('click', () => setMenu(!menu.classList.contains('is-open')));
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('.menu-wrap')) setMenu(false);
|
||||
});
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') { setMenu(false); burger.focus(); }
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
354
design/mockups/my-conferences.html
Normal file
354
design/mockups/my-conferences.html
Normal file
@@ -0,0 +1,354 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>VidConf — Мои конференции</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../tokens.css">
|
||||
<style>
|
||||
/*
|
||||
* «Мои конференции» (фаза R1, пивот) — новый экран: список закреплённых
|
||||
* (постоянных, с повторением) и предстоящих разовых конференций
|
||||
* пользователя. У каждой — ссылка и номер с копированием, ближайшее
|
||||
* вхождение/дата и кнопка входа. Завершённые — переход к саммари, а не
|
||||
* повторный вход (незакреплённая конференция после завершения недоступна
|
||||
* для входа).
|
||||
*/
|
||||
|
||||
html { min-width: 1280px; }
|
||||
|
||||
.topbar {
|
||||
position: sticky; top: 0; z-index: 20;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: var(--space-4) var(--space-8);
|
||||
background: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.brand-mark { display: inline-flex; align-items: center; gap: 8px; font: var(--text-h3); color: var(--color-ink-700); }
|
||||
.brand-mark .logo-mark { width: 22px; height: 22px; flex-shrink: 0; }
|
||||
.topbar-right { display: flex; align-items: center; gap: var(--space-5); }
|
||||
.nav-link { font: var(--text-body); font-weight: 600; color: var(--color-ink-500); text-decoration: none; }
|
||||
.nav-link.is-active { color: var(--color-ink-700); }
|
||||
.avatar-btn { display: flex; align-items: center; gap: 8px; background: var(--color-bg-alt); border: 1px solid var(--color-border); border-radius: var(--radius-full); padding: 6px 14px 6px 6px; }
|
||||
.avatar { width: 28px; height: 28px; border-radius: 50%; background: var(--color-ink-700); color: #fff; display:flex; align-items:center; justify-content:center; font: var(--text-caption); text-transform: none; letter-spacing: 0; }
|
||||
/* Каскад-баг гейта (2026-07-16, 3-я итерация): было `.avatar-btn span`
|
||||
(0,1,1) — перебивало `.avatar { color:#fff }` (0,1,0), инициалы
|
||||
рендерились тёмным ink-900 на индиго-заливке (нечитаемо). Селектор имени
|
||||
сделан адресным — задевает только `.user-name`, не пересекается с
|
||||
`.avatar` ни при каком порядке правил. См. DESIGN_SYSTEM.md §1.5. */
|
||||
.avatar-btn .user-name { font: var(--text-body); font-weight: 600; color: var(--color-ink-900); }
|
||||
|
||||
main { padding: var(--space-8); max-width: 1280px; margin: 0 auto; }
|
||||
|
||||
.page-head { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: var(--space-6); }
|
||||
.page-head h1 { font: var(--text-display-lg); font-family: var(--font-display); color: var(--color-ink-700); margin: 0 0 6px; }
|
||||
.page-head p { font: var(--text-body-lg); color: var(--color-ink-500); margin: 0; }
|
||||
|
||||
.btn { display: inline-flex; align-items: center; gap: 8px; border: none; cursor: pointer; font-family: var(--font-body); white-space: nowrap; transition: background var(--transition-fast), box-shadow var(--transition-fast); }
|
||||
.btn-primary { background: var(--color-accent); color: var(--color-accent-text); font: var(--text-body); font-weight: 700; padding: 11px 22px; border-radius: var(--radius-full); }
|
||||
.btn-primary:hover { background: var(--color-accent-hover); box-shadow: var(--shadow-md); }
|
||||
.btn-primary:active { background: var(--color-accent-active); }
|
||||
.btn-secondary { background: transparent; color: var(--color-ink-700); border: 1.5px solid var(--color-border-pill); font: var(--text-body); font-weight: 600; padding: 9px 18px; border-radius: var(--radius-full); }
|
||||
.btn-secondary:hover { background: var(--color-bg-alt); }
|
||||
:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
|
||||
|
||||
.filters { display: flex; gap: 8px; margin-bottom: var(--space-6); }
|
||||
.chip { display: inline-flex; align-items: center; gap: 6px; padding: 9px 16px; border-radius: var(--radius-full); border: 1.5px solid var(--color-border); background: var(--color-surface); font: var(--text-body); font-weight: 600; color: var(--color-ink-500); }
|
||||
.chip.is-active { border-color: var(--color-ink-700); color: var(--color-ink-700); background: var(--color-bg-alt); }
|
||||
|
||||
.conf-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-5); }
|
||||
|
||||
.conf-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); padding: var(--space-6); display: flex; flex-direction: column; gap: var(--space-4); transition: box-shadow var(--transition-base), border-color var(--transition-base); }
|
||||
.conf-card:hover { box-shadow: var(--shadow-md); border-color: var(--color-border-strong); }
|
||||
.conf-card.is-ended { opacity: .85; }
|
||||
|
||||
.conf-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); }
|
||||
.conf-title-wrap h3 { margin: 0 0 4px; font: var(--text-h3); color: var(--color-ink-900); display: flex; align-items: center; gap: 6px; }
|
||||
.conf-title-wrap p { margin: 0; font: var(--text-body); color: var(--color-ink-500); }
|
||||
|
||||
.conf-badge { display: inline-flex; align-items: center; gap: 5px; padding: 4px 12px; border-radius: var(--radius-full); font: var(--text-caption); white-space: nowrap; }
|
||||
.conf-badge.pinned { background: var(--color-bg-alt); color: var(--color-ink-700); border: 1px solid var(--color-border-strong); }
|
||||
.conf-badge.scheduled { background: var(--color-warning-bg); color: var(--color-warning); }
|
||||
.conf-badge.ended { background: var(--color-bg-alt); color: var(--color-ink-500); }
|
||||
|
||||
.conf-meta-row { display: flex; align-items: center; gap: var(--space-4); font: var(--text-body); color: var(--color-ink-500); }
|
||||
.conf-meta-row .meta-item { display: flex; align-items: center; gap: 6px; }
|
||||
|
||||
.pills-row { display: flex; gap: var(--space-3); }
|
||||
.pill-group { flex: 1; min-width: 0; }
|
||||
.pill-label { display: block; font: var(--text-caption); color: var(--color-ink-400); margin-bottom: 4px; }
|
||||
.code-pill { display: flex; align-items: center; gap: 6px; background: var(--color-bg-alt); border: 1px solid var(--color-border); border-radius: var(--radius-full); padding: 6px 6px 6px 14px; }
|
||||
.pill-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: var(--text-mono-sm); color: var(--color-ink-700); }
|
||||
.copy-btn { position: relative; width: 28px; height: 28px; flex-shrink: 0; border-radius: 50%; border: none; background: var(--color-surface); color: var(--color-ink-500); display: flex; align-items: center; justify-content: center; transition: color var(--transition-fast), box-shadow var(--transition-fast); }
|
||||
.copy-btn:hover { color: var(--color-ink-700); box-shadow: var(--shadow-sm); }
|
||||
.copy-btn .icon-check { display: none; color: var(--color-success); }
|
||||
.copy-btn.is-copied .icon-copy { display: none; }
|
||||
.copy-btn.is-copied .icon-check { display: block; }
|
||||
.copy-toast {
|
||||
position: absolute; bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%);
|
||||
background: var(--color-ink-900); color: #fff; font: var(--text-caption); text-transform: none; letter-spacing: normal;
|
||||
padding: 4px 10px; border-radius: var(--radius-sm); white-space: nowrap;
|
||||
opacity: 0; pointer-events: none; transition: opacity var(--transition-fast);
|
||||
}
|
||||
.copy-btn.is-copied .copy-toast { opacity: 1; }
|
||||
|
||||
.conf-card-footer { display: flex; gap: var(--space-3); margin-top: auto; }
|
||||
.conf-card-footer .btn { flex: 1; justify-content: center; }
|
||||
.icon-btn { width: 40px; height: 40px; flex-shrink: 0; border-radius: 50%; border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-ink-500); display: flex; align-items: center; justify-content: center; }
|
||||
.icon-btn:hover { border-color: var(--color-border-strong); color: var(--color-ink-900); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<svg style="display:none">
|
||||
<symbol id="i-repeat" viewBox="0 0 24 24"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/></symbol>
|
||||
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></symbol>
|
||||
<symbol id="i-check-circle" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||||
<symbol id="i-users" viewBox="0 0 24 24"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></symbol>
|
||||
<symbol id="i-copy" viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></symbol>
|
||||
<symbol id="i-play" viewBox="0 0 24 24"><polygon points="6 3 20 12 6 21 6 3"/></symbol>
|
||||
<symbol id="i-pencil" viewBox="0 0 24 24"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></symbol>
|
||||
<symbol id="i-file-text" viewBox="0 0 24 24"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="16" y2="17"/></symbol>
|
||||
</svg>
|
||||
|
||||
<header class="topbar">
|
||||
<div class="brand-mark"><svg class="logo-mark" viewBox="0 0 32 32" fill="none" aria-hidden="true"><polygon points="21,16 18.5,20.3 13.5,20.3 11,16 13.5,11.7 18.5,11.7" fill="currentColor"/><circle cx="16" cy="16" r="13" stroke="currentColor" stroke-width="2.6" stroke-dasharray="12 6"/></svg> VidConf</div>
|
||||
<nav class="topbar-right">
|
||||
<a href="lobby.html" class="nav-link">Лобби</a>
|
||||
<a href="calendar.html" class="nav-link">Календарь</a>
|
||||
<a href="my-conferences.html" class="nav-link is-active">Мои конференции</a>
|
||||
<a href="admin.html" class="nav-link">Админка</a>
|
||||
<button class="avatar-btn"><span class="avatar">АИ</span><span class="user-name">Анна Иванова</span></button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Мои конференции</h1>
|
||||
<p>5 конференций · 3 закреплённые с повторением</p>
|
||||
</div>
|
||||
<a href="calendar.html" class="btn btn-primary">Запланировать новую</a>
|
||||
</div>
|
||||
|
||||
<div class="filters">
|
||||
<button class="chip is-active">Все</button>
|
||||
<button class="chip"><svg class="icon" style="width:14px;height:14px"><use href="#i-repeat"/></svg>Закреплённые</button>
|
||||
<button class="chip"><svg class="icon" style="width:14px;height:14px"><use href="#i-clock"/></svg>Предстоящие</button>
|
||||
<button class="chip">Завершённые</button>
|
||||
</div>
|
||||
|
||||
<section class="conf-grid">
|
||||
|
||||
<!-- Закреплена, еженедельно -->
|
||||
<article class="conf-card">
|
||||
<div class="conf-card-head">
|
||||
<div class="conf-title-wrap">
|
||||
<h3>Ревью спринта</h3>
|
||||
<p>По вторникам и четвергам, 11:00–11:45</p>
|
||||
</div>
|
||||
<span class="conf-badge pinned"><svg class="icon" style="width:12px;height:12px"><use href="#i-repeat"/></svg>Закреплена</span>
|
||||
</div>
|
||||
<div class="conf-meta-row">
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-clock"/></svg>Ближайшее: вт, 22 июля, 11:00</span>
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-users"/></svg>8 участников</span>
|
||||
</div>
|
||||
<div class="pills-row">
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Ссылка</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">vidconf.ru/j/sprint-review</span>
|
||||
<button class="copy-btn" data-copy="https://vidconf.ru/j/sprint-review" aria-label="Скопировать ссылку">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Номер</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">8842 104</span>
|
||||
<button class="copy-btn" data-copy="8842 104" aria-label="Скопировать номер">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conf-card-footer">
|
||||
<a href="room.html" class="btn btn-primary"><svg class="icon" style="width:16px;height:16px"><use href="#i-play"/></svg>Войти</a>
|
||||
<button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:17px;height:17px"><use href="#i-pencil"/></svg></button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Закреплена, ежемесячно, закрытая паролем -->
|
||||
<article class="conf-card">
|
||||
<div class="conf-card-head">
|
||||
<div class="conf-title-wrap">
|
||||
<h3><svg class="icon" style="width:15px;height:15px;flex-shrink:0"><use href="#i-lock"/></svg>Синк по релизу 2.4</h3>
|
||||
<p>Раз в месяц, 15-го числа, 14:00–15:00</p>
|
||||
</div>
|
||||
<span class="conf-badge pinned"><svg class="icon" style="width:12px;height:12px"><use href="#i-repeat"/></svg>Закреплена</span>
|
||||
</div>
|
||||
<div class="conf-meta-row">
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-clock"/></svg>Ближайшее: 15 августа, 14:00</span>
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-users"/></svg>4 участника</span>
|
||||
</div>
|
||||
<div class="pills-row">
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Ссылка</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">vidconf.ru/j/release-sync</span>
|
||||
<button class="copy-btn" data-copy="https://vidconf.ru/j/release-sync" aria-label="Скопировать ссылку">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Номер</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">5217 933</span>
|
||||
<button class="copy-btn" data-copy="5217 933" aria-label="Скопировать номер">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conf-card-footer">
|
||||
<a href="join.html" class="btn btn-primary"><svg class="icon" style="width:16px;height:16px"><use href="#i-lock"/></svg>Войти по паролю</a>
|
||||
<button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:17px;height:17px"><use href="#i-pencil"/></svg></button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Предстоящая, разовая -->
|
||||
<article class="conf-card">
|
||||
<div class="conf-card-head">
|
||||
<div class="conf-title-wrap">
|
||||
<h3>1:1 с руководителем</h3>
|
||||
<p>Разовая · 21 июля, 15:00–15:30</p>
|
||||
</div>
|
||||
<span class="conf-badge scheduled"><svg class="icon" style="width:12px;height:12px"><use href="#i-clock"/></svg>Предстоящая</span>
|
||||
</div>
|
||||
<div class="conf-meta-row">
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-users"/></svg>2 участника</span>
|
||||
</div>
|
||||
<div class="pills-row">
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Ссылка</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">vidconf.ru/j/one-on-one</span>
|
||||
<button class="copy-btn" data-copy="https://vidconf.ru/j/one-on-one" aria-label="Скопировать ссылку">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Номер</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">2094 471</span>
|
||||
<button class="copy-btn" data-copy="2094 471" aria-label="Скопировать номер">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conf-card-footer">
|
||||
<button class="btn btn-secondary" style="flex:1" disabled><svg class="icon" style="width:16px;height:16px"><use href="#i-clock"/></svg>Начнётся через 3 дня</button>
|
||||
<button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:17px;height:17px"><use href="#i-pencil"/></svg></button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Завершена (незакреплённая — повторный вход недоступен, только саммари) -->
|
||||
<article class="conf-card is-ended">
|
||||
<div class="conf-card-head">
|
||||
<div class="conf-title-wrap">
|
||||
<h3>Итоги квартала</h3>
|
||||
<p>Разовая · 29 июня, 12:00–13:00</p>
|
||||
</div>
|
||||
<span class="conf-badge ended"><svg class="icon" style="width:12px;height:12px"><use href="#i-check-circle"/></svg>Завершена</span>
|
||||
</div>
|
||||
<div class="conf-meta-row">
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-users"/></svg>14 участников</span>
|
||||
</div>
|
||||
<div class="conf-card-footer">
|
||||
<a href="email-summary.html" class="btn btn-secondary" style="flex:1"><svg class="icon" style="width:16px;height:16px"><use href="#i-file-text"/></svg>Открыть саммари</a>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Закреплена, каждые N дней -->
|
||||
<article class="conf-card">
|
||||
<div class="conf-card-head">
|
||||
<div class="conf-title-wrap">
|
||||
<h3>Ежеквартальный отчёт</h3>
|
||||
<p>Каждые 90 дней, 10:00–11:00</p>
|
||||
</div>
|
||||
<span class="conf-badge pinned"><svg class="icon" style="width:12px;height:12px"><use href="#i-repeat"/></svg>Закреплена</span>
|
||||
</div>
|
||||
<div class="conf-meta-row">
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-clock"/></svg>Ближайшее: 3 октября, 10:00</span>
|
||||
<span class="meta-item"><svg class="icon" style="width:15px;height:15px"><use href="#i-users"/></svg>22 участника</span>
|
||||
</div>
|
||||
<div class="pills-row">
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Ссылка</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">vidconf.ru/j/q-report</span>
|
||||
<button class="copy-btn" data-copy="https://vidconf.ru/j/q-report" aria-label="Скопировать ссылку">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pill-group">
|
||||
<span class="pill-label">Номер</span>
|
||||
<div class="code-pill">
|
||||
<span class="pill-value">6630 812</span>
|
||||
<button class="copy-btn" data-copy="6630 812" aria-label="Скопировать номер">
|
||||
<svg class="icon icon-copy" style="width:14px;height:14px"><use href="#i-copy"/></svg>
|
||||
<svg class="icon icon-check" style="width:14px;height:14px"><use href="#i-check"/></svg>
|
||||
<span class="copy-toast" aria-hidden="true">Скопировано</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conf-card-footer">
|
||||
<button class="btn btn-secondary" style="flex:1" disabled><svg class="icon" style="width:16px;height:16px"><use href="#i-clock"/></svg>Начнётся 3 октября</button>
|
||||
<button class="icon-btn" aria-label="Редактировать"><svg class="icon" style="width:17px;height:17px"><use href="#i-pencil"/></svg></button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Копирование ссылки/номера в буфер обмена + временная подсказка «Скопировано».
|
||||
document.querySelectorAll('.copy-btn').forEach((btn) => {
|
||||
const originalLabel = btn.getAttribute('aria-label');
|
||||
btn.addEventListener('click', () => {
|
||||
const text = btn.dataset.copy || '';
|
||||
if (navigator.clipboard) { navigator.clipboard.writeText(text).catch(() => {}); }
|
||||
btn.classList.add('is-copied');
|
||||
btn.setAttribute('aria-label', 'Скопировано');
|
||||
setTimeout(() => {
|
||||
btn.classList.remove('is-copied');
|
||||
btn.setAttribute('aria-label', originalLabel);
|
||||
}, 1500);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
336
design/mockups/room.html
Normal file
336
design/mockups/room.html
Normal file
@@ -0,0 +1,336 @@
|
||||
<!doctype html>
|
||||
<html lang="ru" data-theme="room">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>VidConf — Комната конференции</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../tokens.css">
|
||||
<style>
|
||||
html { min-width: 1280px; }
|
||||
body { background: var(--color-room-bg); color: var(--color-room-text-primary); overflow-x: hidden; }
|
||||
|
||||
.room-shell { display: flex; flex-direction: column; height: 100vh; }
|
||||
|
||||
/* ---------- Топбар ---------- */
|
||||
.room-topbar {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: var(--space-3) var(--space-6);
|
||||
background: var(--color-room-surface);
|
||||
border-bottom: 1px solid var(--color-room-tile-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.room-title-block h1 { font: var(--text-h3); margin: 0 0 2px; color: var(--color-room-text-primary); }
|
||||
.room-title-block p { font: var(--text-body); margin: 0; color: var(--color-room-text-secondary); display:flex; align-items:center; gap:8px; }
|
||||
.rec-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--color-room-danger); box-shadow: 0 0 0 3px rgba(235,147,161,.3); }
|
||||
|
||||
.layout-switch { display: flex; align-items: center; gap: var(--space-2); }
|
||||
.icon-btn {
|
||||
width: 40px; height: 40px; border-radius: var(--radius-md);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: transparent; border: 1px solid transparent; color: var(--color-room-text-secondary);
|
||||
}
|
||||
.icon-btn:hover { background: var(--color-room-tile-hover); color: var(--color-room-text-primary); }
|
||||
.icon-btn.is-active { background: var(--color-room-tile); color: var(--color-room-speaker-ring); border-color: var(--color-room-speaker-ring); }
|
||||
|
||||
/* ---------- Основная область: превью + крупные плитки + чат ---------- */
|
||||
.room-main { display: flex; flex: 1; min-height: 0; }
|
||||
.stage { flex: 1; display: flex; flex-direction: column; padding: var(--space-5) var(--space-6); gap: var(--space-4); min-width: 0; }
|
||||
|
||||
.preview-row { display: flex; align-items: center; gap: var(--space-3); }
|
||||
.preview-scroll { display: flex; gap: var(--space-3); overflow-x: auto; flex: 1; padding-bottom: 4px; }
|
||||
.preview-scroll::-webkit-scrollbar { height: 6px; }
|
||||
.preview-scroll::-webkit-scrollbar-thumb { background: var(--color-room-tile-border); border-radius: 3px; }
|
||||
|
||||
.scroll-arrow {
|
||||
width: 32px; height: 32px; flex-shrink: 0; border-radius: 50%;
|
||||
background: var(--color-room-surface); border: 1px solid var(--color-room-tile-border);
|
||||
color: var(--color-room-text-secondary); display:flex; align-items:center; justify-content:center;
|
||||
}
|
||||
.scroll-arrow:hover { color: var(--color-room-text-primary); }
|
||||
|
||||
.tile {
|
||||
position: relative; background: var(--color-room-tile); border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--color-room-tile-border); overflow: hidden;
|
||||
}
|
||||
.tile-preview { width: 160px; height: 100px; flex-shrink: 0; }
|
||||
.tile-name {
|
||||
position: absolute; top: 8px; left: 8px; font: var(--text-body);
|
||||
font-weight: 600; color: var(--color-room-text-primary);
|
||||
background: rgba(0,0,0,.35); padding: 2px 8px; border-radius: var(--radius-sm);
|
||||
max-width: calc(100% - 16px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
}
|
||||
.tile-preview .tile-name { font-size: 12px; padding: 1px 6px; }
|
||||
.tile-avatar {
|
||||
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.tile-avatar svg { width: 38%; height: 38%; color: #555; }
|
||||
.tile-preview .tile-avatar svg { width: 30%; height: 30%; }
|
||||
|
||||
.mic-indicator {
|
||||
position: absolute; bottom: 8px; left: 8px; width: 26px; height: 26px; border-radius: 50%;
|
||||
background: rgba(0,0,0,.4); display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.mic-indicator.on { color: var(--color-room-mic-on); }
|
||||
.mic-indicator.off { color: var(--color-room-mic-off); }
|
||||
.mic-indicator .icon { width: 15px; height: 15px; }
|
||||
.tile-preview .mic-indicator { width: 20px; height: 20px; bottom: 6px; left: 6px; }
|
||||
.tile-preview .mic-indicator .icon { width: 12px; height: 12px; }
|
||||
|
||||
.tile.is-speaking { border: var(--border-speaker) solid var(--color-room-speaker-ring); box-shadow: var(--shadow-speaker-glow); }
|
||||
|
||||
.stage-tiles { flex: 1; display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); min-height: 0; }
|
||||
.tile-large { min-height: 0; }
|
||||
.tile-corner-actions { position: absolute; top: 10px; right: 10px; display: flex; gap: 6px; opacity: .85; }
|
||||
.tile-corner-actions button { width: 30px; height: 30px; border-radius: var(--radius-sm); background: rgba(0,0,0,.35); border: none; color: var(--color-room-text-primary); display:flex; align-items:center; justify-content:center; }
|
||||
.tile-corner-actions button:hover { background: rgba(0,0,0,.55); }
|
||||
|
||||
.video-off-badge {
|
||||
position: absolute; bottom: 8px; right: 8px; background: rgba(0,0,0,.4);
|
||||
border-radius: var(--radius-sm); padding: 4px 8px; display:flex; align-items:center; gap:4px;
|
||||
font: var(--text-caption); color: var(--color-room-text-secondary); text-transform:none; letter-spacing:normal;
|
||||
}
|
||||
|
||||
/* ---------- Панель чата ---------- */
|
||||
.chat-panel {
|
||||
width: 320px; flex-shrink: 0; background: var(--color-room-surface-raised);
|
||||
border-left: 1px solid var(--color-room-tile-border);
|
||||
display: flex; flex-direction: column; box-shadow: var(--shadow-room-panel);
|
||||
}
|
||||
.chat-head { display:flex; align-items:center; justify-content:space-between; padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--color-room-tile-border); }
|
||||
.chat-head h2 { font: var(--text-h3); margin: 0; }
|
||||
.chat-head button { background: none; border: none; color: var(--color-room-text-secondary); }
|
||||
.chat-head button:hover { color: var(--color-room-text-primary); }
|
||||
|
||||
.chat-messages { flex: 1; overflow-y: auto; padding: var(--space-5); display: flex; flex-direction: column; gap: var(--space-4); }
|
||||
.msg .msg-meta { display:flex; align-items:baseline; gap: 8px; margin-bottom: 4px; }
|
||||
.msg .msg-author { font: var(--text-body); font-weight: 700; color: var(--color-room-mic-on); }
|
||||
.msg .msg-time { font: 12px var(--font-body); color: var(--color-room-text-tertiary); }
|
||||
.msg .msg-bubble { background: var(--color-room-tile); border-radius: var(--radius-md); padding: 10px 14px; font: var(--text-body); color: var(--color-room-text-primary); }
|
||||
|
||||
.chat-input-row { padding: var(--space-4); border-top: 1px solid var(--color-room-tile-border); display: flex; gap: 8px; }
|
||||
.chat-input-row input {
|
||||
flex: 1; background: var(--color-room-tile); border: 1px solid var(--color-room-tile-border);
|
||||
border-radius: var(--radius-full); padding: 10px 16px; color: var(--color-room-text-primary); font: var(--text-body-lg);
|
||||
}
|
||||
.chat-input-row input::placeholder { color: var(--color-room-text-tertiary); }
|
||||
.chat-input-row button {
|
||||
width: 42px; height: 42px; border-radius: 50%; background: var(--color-room-mic-on); border: none;
|
||||
color: #10331f; display:flex; align-items:center; justify-content:center; flex-shrink:0;
|
||||
}
|
||||
|
||||
/* ---------- Нижний тулбар ---------- */
|
||||
.room-toolbar {
|
||||
display: flex; align-items: center; justify-content: center; gap: var(--space-2);
|
||||
background: var(--color-room-surface); border-top: 1px solid var(--color-room-tile-border);
|
||||
padding: var(--space-3) var(--space-6); flex-shrink: 0;
|
||||
}
|
||||
.tb-btn {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 4px;
|
||||
background: transparent; border: none; padding: 8px 18px; border-radius: var(--radius-md);
|
||||
color: var(--color-room-text-primary); min-width: 76px;
|
||||
}
|
||||
.tb-btn .icon-shell {
|
||||
width: 48px; height: 48px; border-radius: 50%; display:flex; align-items:center; justify-content:center;
|
||||
border: 1.5px solid var(--color-room-tile-border); background: var(--color-room-tile);
|
||||
}
|
||||
.tb-btn span.label { font: var(--text-caption); text-transform: none; letter-spacing: normal; color: var(--color-room-text-secondary); font-weight: 500; }
|
||||
.tb-btn:hover .icon-shell { background: var(--color-room-tile-hover); }
|
||||
|
||||
.tb-btn.is-on .icon-shell { color: var(--color-room-text-primary); }
|
||||
.tb-btn.is-off .icon-shell { background: var(--color-room-mic-off); border-color: var(--color-room-mic-off); color: #3a0f16; }
|
||||
.tb-btn.is-off span.label { color: var(--color-room-mic-off); font-weight: 700; }
|
||||
|
||||
.tb-btn.is-positive-active .icon-shell { background: var(--color-room-mic-on); border-color: var(--color-room-mic-on); color: #0e3320; }
|
||||
.tb-btn.is-positive-active span.label { color: var(--color-room-mic-on); font-weight: 700; }
|
||||
|
||||
.tb-btn.is-panel-open .icon-shell { background: var(--color-room-surface-raised); border-color: var(--color-room-speaker-ring); color: var(--color-room-speaker-ring); }
|
||||
|
||||
.tb-btn.danger .icon-shell { border-color: var(--color-room-danger); color: var(--color-room-danger); background: transparent; }
|
||||
.tb-btn.danger:hover .icon-shell { background: var(--color-room-danger-bg); color: #fff; border-color: var(--color-room-danger-bg); }
|
||||
.tb-btn.danger span.label { color: var(--color-room-danger); font-weight: 700; }
|
||||
|
||||
.badge-count {
|
||||
position: absolute; top: -2px; right: -2px; background: var(--color-room-mic-on); color: #0e3320;
|
||||
font-size: 10px; font-weight: 700; width: 16px; height: 16px; border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
|
||||
/* ---------- Демонстрация состояния "чат свёрнут" ---------- */
|
||||
.collapsed-demo {
|
||||
margin-top: var(--space-6); padding: var(--space-5) var(--space-6);
|
||||
background: var(--color-room-surface); border-top: 1px solid var(--color-room-tile-border);
|
||||
}
|
||||
.collapsed-demo p.label { font: var(--text-caption); color: var(--color-room-text-secondary); text-transform: none; letter-spacing: normal; margin: 0 0 10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<svg style="display:none">
|
||||
<symbol id="i-mic" viewBox="0 0 24 24"><path d="M12 2a3 3 0 0 0-3 3v6a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v1a7 7 0 0 1-14 0v-1"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="8" y1="22" x2="16" y2="22"/></symbol>
|
||||
<symbol id="i-mic-off" viewBox="0 0 24 24"><path d="M9 9v3a3 3 0 0 0 4.24 2.73"/><path d="M15 9.34V5a3 3 0 0 0-5.94-.6"/><path d="M19 10v1a7 7 0 0 1-11.5 5.34"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="8" y1="22" x2="16" y2="22"/><line x1="2" y1="2" x2="22" y2="22"/></symbol>
|
||||
<symbol id="i-video" viewBox="0 0 24 24"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="m22 8-6 4 6 4V8Z"/></symbol>
|
||||
<symbol id="i-video-off" viewBox="0 0 24 24"><path d="M16 16v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h1"/><path d="M9.5 6H14a2 2 0 0 1 2 2v4.5"/><path d="m22 8-6 4"/><line x1="2" y1="2" x2="22" y2="22"/></symbol>
|
||||
<symbol id="i-share" viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/><path d="M9 10.5 12 7l3 3.5"/><line x1="12" y1="7" x2="12" y2="14"/></symbol>
|
||||
<symbol id="i-chat" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="13" rx="3"/><path d="m7 17-3 4v-4"/></symbol>
|
||||
<symbol id="i-users" viewBox="0 0 24 24"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></symbol>
|
||||
<symbol id="i-logout" viewBox="0 0 24 24"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></symbol>
|
||||
<symbol id="i-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M6 21v-2a6 6 0 0 1 12 0v2"/></symbol>
|
||||
<symbol id="i-grid" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/></symbol>
|
||||
<symbol id="i-columns" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></symbol>
|
||||
<symbol id="i-grid2" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="12" y1="3" x2="12" y2="21"/><line x1="3" y1="12" x2="21" y2="12"/></symbol>
|
||||
<symbol id="i-settings" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z"/></symbol>
|
||||
<symbol id="i-maximize" viewBox="0 0 24 24"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></symbol>
|
||||
<symbol id="i-more" viewBox="0 0 24 24"><circle cx="12" cy="5" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="12" cy="19" r="1.5"/></symbol>
|
||||
<symbol id="i-chevron-left" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></symbol>
|
||||
<symbol id="i-chevron-right" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></symbol>
|
||||
<symbol id="i-send" viewBox="0 0 24 24"><path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/></symbol>
|
||||
<symbol id="i-x" viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></symbol>
|
||||
</svg>
|
||||
|
||||
<div class="room-shell">
|
||||
|
||||
<header class="room-topbar">
|
||||
<div class="room-title-block">
|
||||
<h1>Переговорная №42 · Спринт-ревью команды платформы</h1>
|
||||
<p><span class="rec-dot" aria-hidden="true"></span> Идёт запись · 00:24:11 · 8 участников</p>
|
||||
</div>
|
||||
<div class="layout-switch">
|
||||
<button class="icon-btn is-active" aria-label="Раскладка: лента + спикер"><svg class="icon"><use href="#i-grid"/></svg></button>
|
||||
<button class="icon-btn" aria-label="Раскладка: колонки"><svg class="icon"><use href="#i-columns"/></svg></button>
|
||||
<button class="icon-btn" aria-label="Раскладка: равная сетка"><svg class="icon"><use href="#i-grid2"/></svg></button>
|
||||
<button class="icon-btn" aria-label="Настройки"><svg class="icon"><use href="#i-settings"/></svg></button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="room-main">
|
||||
<section class="stage">
|
||||
|
||||
<!-- Лента превью участников -->
|
||||
<div class="preview-row">
|
||||
<button class="scroll-arrow" aria-label="Прокрутить влево"><svg class="icon" style="width:16px;height:16px"><use href="#i-chevron-left"/></svg></button>
|
||||
<div class="preview-scroll">
|
||||
<div class="tile tile-preview">
|
||||
<div class="tile-name">Дмитрий Кошелев</div>
|
||||
<div class="tile-avatar"><svg class="icon"><use href="#i-user"/></svg></div>
|
||||
<div class="mic-indicator off"><svg class="icon"><use href="#i-mic-off"/></svg></div>
|
||||
</div>
|
||||
<div class="tile tile-preview">
|
||||
<div class="tile-name">Ольга Панина</div>
|
||||
<div class="tile-avatar"><svg class="icon"><use href="#i-user"/></svg></div>
|
||||
<div class="mic-indicator off"><svg class="icon"><use href="#i-mic-off"/></svg></div>
|
||||
</div>
|
||||
<div class="tile tile-preview">
|
||||
<div class="tile-name">Виктор Седых</div>
|
||||
<div class="tile-avatar"><svg class="icon"><use href="#i-user"/></svg></div>
|
||||
<div class="mic-indicator on"><svg class="icon"><use href="#i-mic"/></svg></div>
|
||||
</div>
|
||||
<div class="tile tile-preview">
|
||||
<div class="tile-name">Настя Титова</div>
|
||||
<div class="tile-avatar"><svg class="icon"><use href="#i-user"/></svg></div>
|
||||
<div class="mic-indicator off"><svg class="icon"><use href="#i-mic-off"/></svg></div>
|
||||
</div>
|
||||
<div class="tile tile-preview">
|
||||
<div class="tile-name">Роман Матвеев</div>
|
||||
<div class="tile-avatar"><svg class="icon"><use href="#i-user"/></svg></div>
|
||||
<div class="mic-indicator on"><svg class="icon"><use href="#i-mic"/></svg></div>
|
||||
</div>
|
||||
<div class="tile tile-preview">
|
||||
<div class="tile-name">Елена Жукова</div>
|
||||
<div class="tile-avatar"><svg class="icon"><use href="#i-user"/></svg></div>
|
||||
<div class="mic-indicator off"><svg class="icon"><use href="#i-mic-off"/></svg></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="scroll-arrow" aria-label="Прокрутить вправо"><svg class="icon" style="width:16px;height:16px"><use href="#i-chevron-right"/></svg></button>
|
||||
</div>
|
||||
|
||||
<!-- Крупные плитки: активный спикер + участник -->
|
||||
<div class="stage-tiles">
|
||||
<div class="tile tile-large is-speaking">
|
||||
<div class="tile-name">Игорь Соколов</div>
|
||||
<div class="tile-corner-actions">
|
||||
<button aria-label="Развернуть на весь экран"><svg class="icon" style="width:16px;height:16px"><use href="#i-maximize"/></svg></button>
|
||||
<button aria-label="Ещё действия"><svg class="icon" style="width:16px;height:16px"><use href="#i-more"/></svg></button>
|
||||
</div>
|
||||
<div class="tile-avatar"><svg class="icon" style="color:#8a8a8a"><use href="#i-user"/></svg></div>
|
||||
<div class="mic-indicator on"><svg class="icon"><use href="#i-mic"/></svg></div>
|
||||
</div>
|
||||
<div class="tile tile-large">
|
||||
<div class="tile-name">Мария Кузнецова</div>
|
||||
<div class="tile-corner-actions">
|
||||
<button aria-label="Развернуть на весь экран"><svg class="icon" style="width:16px;height:16px"><use href="#i-maximize"/></svg></button>
|
||||
<button aria-label="Ещё действия"><svg class="icon" style="width:16px;height:16px"><use href="#i-more"/></svg></button>
|
||||
</div>
|
||||
<div class="tile-avatar"><svg class="icon" style="color:#8a8a8a"><use href="#i-user"/></svg></div>
|
||||
<div class="mic-indicator off"><svg class="icon"><use href="#i-mic-off"/></svg></div>
|
||||
<div class="video-off-badge"><svg class="icon" style="width:12px;height:12px"><use href="#i-video-off"/></svg>Камера выключена</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Панель чата: РАЗВЁРНУТОЕ состояние -->
|
||||
<aside class="chat-panel">
|
||||
<div class="chat-head">
|
||||
<h2>Чат встречи</h2>
|
||||
<button aria-label="Свернуть чат"><svg class="icon"><use href="#i-x"/></svg></button>
|
||||
</div>
|
||||
<div class="chat-messages">
|
||||
<div class="msg">
|
||||
<div class="msg-meta"><span class="msg-author">Виктор Седых</span><span class="msg-time">14:02</span></div>
|
||||
<div class="msg-bubble">Отправил ссылку на дизайн-макеты в описании встречи</div>
|
||||
</div>
|
||||
<div class="msg">
|
||||
<div class="msg-meta"><span class="msg-author">Ольга Панина</span><span class="msg-time">14:03</span></div>
|
||||
<div class="msg-bubble">Принято, посмотрю после демо API</div>
|
||||
</div>
|
||||
<div class="msg">
|
||||
<div class="msg-meta"><span class="msg-author">Игорь Соколов</span><span class="msg-time">14:05</span></div>
|
||||
<div class="msg-bubble">Начинаем со спринт-ревью, затем — блок вопросов</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-input-row">
|
||||
<input type="text" placeholder="Написать сообщение…">
|
||||
<button aria-label="Отправить"><svg class="icon" style="width:18px;height:18px"><use href="#i-send"/></svg></button>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<!-- Нижний тулбар -->
|
||||
<footer class="room-toolbar">
|
||||
<button class="tb-btn is-off">
|
||||
<span class="icon-shell"><svg class="icon"><use href="#i-mic-off"/></svg></span>
|
||||
<span class="label">Микрофон</span>
|
||||
</button>
|
||||
<button class="tb-btn is-on">
|
||||
<span class="icon-shell"><svg class="icon"><use href="#i-video"/></svg></span>
|
||||
<span class="label">Камера</span>
|
||||
</button>
|
||||
<button class="tb-btn is-positive-active">
|
||||
<span class="icon-shell"><svg class="icon"><use href="#i-share"/></svg></span>
|
||||
<span class="label">Демонстрация</span>
|
||||
</button>
|
||||
<button class="tb-btn is-panel-open">
|
||||
<span class="icon-shell" style="position:relative"><svg class="icon"><use href="#i-chat"/></svg><span class="badge-count">3</span></span>
|
||||
<span class="label">Чат</span>
|
||||
</button>
|
||||
<button class="tb-btn is-on">
|
||||
<span class="icon-shell"><svg class="icon"><use href="#i-users"/></svg></span>
|
||||
<span class="label">Участники</span>
|
||||
</button>
|
||||
<button class="tb-btn danger">
|
||||
<span class="icon-shell"><svg class="icon"><use href="#i-logout"/></svg></span>
|
||||
<span class="label">Выйти</span>
|
||||
</button>
|
||||
</footer>
|
||||
|
||||
<!-- Документация состояния: чат свёрнут -->
|
||||
<div class="collapsed-demo">
|
||||
<p class="label">Справочно — состояние тулбара, когда панель чата свёрнута (иконка без подсветки, счётчик непрочитанных сохраняется):</p>
|
||||
<button class="tb-btn is-on" style="display:inline-flex">
|
||||
<span class="icon-shell" style="position:relative"><svg class="icon"><use href="#i-chat"/></svg><span class="badge-count">3</span></span>
|
||||
<span class="label">Чат</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
236
design/tailwind.md
Normal file
236
design/tailwind.md
Normal file
@@ -0,0 +1,236 @@
|
||||
# Маппинг токенов в Tailwind (для frontend-dev)
|
||||
|
||||
Источник значений — `design/tokens.css` (единственный источник истины).
|
||||
Ниже — как подключить их в `tailwind.config.ts` проекта `frontend/` через
|
||||
`tailwind.config` + CSS-переменные, без дублирования значений в JS.
|
||||
|
||||
## 1. Подключение
|
||||
|
||||
1. Скопировать (или импортировать) `design/tokens.css` в `frontend/src/styles/tokens.css`,
|
||||
подключить в корневом `index.css` до Tailwind-директив:
|
||||
```css
|
||||
@import './tokens.css';
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
```
|
||||
2. В `tailwind.config.ts` **не хардкодить hex** — ссылаться на CSS-переменные,
|
||||
чтобы тема комнаты (`[data-theme="room"]`) и тёмная тема оболочки
|
||||
(`[data-theme="dark"]` / `prefers-color-scheme: dark`, см. ниже §1a)
|
||||
переопределяли значения без пересборки Tailwind:
|
||||
|
||||
```ts
|
||||
import type { Config } from 'tailwindcss'
|
||||
|
||||
export default {
|
||||
darkMode: ['selector', '[data-theme="room"]'], // room-тема — не системная OS dark, а локальный скоуп
|
||||
content: ['./src/**/*.{ts,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
bg: 'var(--color-bg)',
|
||||
'bg-alt': 'var(--color-bg-alt)',
|
||||
surface: 'var(--color-surface)',
|
||||
border: {
|
||||
DEFAULT: 'var(--color-border)',
|
||||
strong: 'var(--color-border-strong)',
|
||||
pill: 'var(--color-border-pill)',
|
||||
},
|
||||
ink: {
|
||||
900: 'var(--color-ink-900)',
|
||||
700: 'var(--color-ink-700)',
|
||||
500: 'var(--color-ink-500)',
|
||||
400: 'var(--color-ink-400)',
|
||||
300: 'var(--color-ink-300)',
|
||||
},
|
||||
// Акцент не обязан быть зелёным — см. DESIGN_SYSTEM.md §1.1.
|
||||
accent: {
|
||||
DEFAULT: 'var(--color-accent)',
|
||||
hover: 'var(--color-accent-hover)',
|
||||
active: 'var(--color-accent-active)',
|
||||
text: 'var(--color-accent-text)',
|
||||
},
|
||||
success: { DEFAULT: 'var(--color-success)', bg: 'var(--color-success-bg)', dot: 'var(--color-success-dot)' },
|
||||
danger: { DEFAULT: 'var(--color-danger)', bg: 'var(--color-danger-bg)', dot: 'var(--color-danger-dot)' },
|
||||
warning: { DEFAULT: 'var(--color-warning)', bg: 'var(--color-warning-bg)', dot: 'var(--color-warning-dot)' },
|
||||
focus: 'var(--color-focus-ring)',
|
||||
|
||||
// Тема комнаты — доступна везде как room-* (реально применяется только
|
||||
// внутри [data-theme="room"], т.к. сами переменные там переопределены)
|
||||
room: {
|
||||
bg: 'var(--color-room-bg)',
|
||||
surface: 'var(--color-room-surface)',
|
||||
'surface-raised': 'var(--color-room-surface-raised)',
|
||||
tile: 'var(--color-room-tile)',
|
||||
'tile-border': 'var(--color-room-tile-border)',
|
||||
'tile-hover': 'var(--color-room-tile-hover)',
|
||||
'text-primary': 'var(--color-room-text-primary)',
|
||||
'text-secondary': 'var(--color-room-text-secondary)',
|
||||
'text-tertiary': 'var(--color-room-text-tertiary)',
|
||||
'mic-on': 'var(--color-room-mic-on)',
|
||||
'mic-off': 'var(--color-room-mic-off)',
|
||||
'speaker-ring': 'var(--color-room-speaker-ring)',
|
||||
danger: 'var(--color-room-danger)',
|
||||
'danger-bg': 'var(--color-room-danger-bg)',
|
||||
'danger-bg-hover': 'var(--color-room-danger-bg-hover)',
|
||||
focus: 'var(--color-room-focus-ring)',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
display: ['Unbounded', 'Arial Rounded MT Bold', '-apple-system', 'Segoe UI', 'sans-serif'],
|
||||
body: ['Inter', '-apple-system', 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', 'sans-serif'],
|
||||
mono: ['JetBrains Mono', 'SFMono-Regular', 'Menlo', 'Consolas', 'monospace'],
|
||||
},
|
||||
fontSize: {
|
||||
'display-2xl': ['64px', { lineHeight: '1.05', fontWeight: '700' }],
|
||||
'display-xl': ['48px', { lineHeight: '1.1', fontWeight: '700' }],
|
||||
'display-lg': ['34px', { lineHeight: '1.2', fontWeight: '600' }],
|
||||
h1: ['28px', { lineHeight: '1.25', fontWeight: '700' }],
|
||||
h2: ['22px', { lineHeight: '1.3', fontWeight: '600' }],
|
||||
h3: ['18px', { lineHeight: '1.35', fontWeight: '600' }],
|
||||
'body-lg': ['16px', { lineHeight: '1.5' }],
|
||||
body: ['14px', { lineHeight: '1.5' }],
|
||||
caption: ['12px', { lineHeight: '1.4', letterSpacing: '.04em', fontWeight: '600' }],
|
||||
'mono-sm': ['13px', { lineHeight: '1.4', fontWeight: '500' }],
|
||||
},
|
||||
spacing: {
|
||||
1: 'var(--space-1)', 2: 'var(--space-2)', 3: 'var(--space-3)',
|
||||
4: 'var(--space-4)', 5: 'var(--space-5)', 6: 'var(--space-6)',
|
||||
8: 'var(--space-8)', 10: 'var(--space-10)', 12: 'var(--space-12)',
|
||||
16: 'var(--space-16)', 20: 'var(--space-20)', 24: 'var(--space-24)',
|
||||
},
|
||||
borderRadius: {
|
||||
sm: 'var(--radius-sm)', md: 'var(--radius-md)', lg: 'var(--radius-lg)',
|
||||
xl: 'var(--radius-xl)', '2xl': 'var(--radius-2xl)', '3xl': 'var(--radius-3xl)',
|
||||
full: 'var(--radius-full)',
|
||||
},
|
||||
boxShadow: {
|
||||
sm: 'var(--shadow-sm)', md: 'var(--shadow-md)', lg: 'var(--shadow-lg)',
|
||||
glass: 'var(--shadow-glass)',
|
||||
'speaker-glow': 'var(--shadow-speaker-glow)',
|
||||
'room-panel': 'var(--shadow-room-panel)',
|
||||
},
|
||||
screens: {
|
||||
// desktop-first: минимальная поддерживаемая ширина — 1280px
|
||||
min: { raw: '(min-width: 1280px)' },
|
||||
},
|
||||
transitionDuration: {
|
||||
fast: '120ms',
|
||||
base: '200ms',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
} satisfies Config
|
||||
```
|
||||
|
||||
## 1a. Тёмная тема оболочки — контракт для frontend
|
||||
|
||||
Механизм (детали и обоснование — `design/DESIGN_SYSTEM.md` §0.1/§1.2b,
|
||||
реализация значений — `design/tokens.css`):
|
||||
|
||||
- Атрибут `data-theme="dark"` / `data-theme="light"` на `<html>` — явный
|
||||
выбор пользователя (persist в localStorage/профиле); без атрибута — дефолт
|
||||
по `@media (prefers-color-scheme: dark)`.
|
||||
- Оба триггера переопределяют **те же самые** CSS-переменные, что и светлый
|
||||
`:root` (`--color-bg`, `--color-ink-900`, `--color-accent`, …). Так как
|
||||
весь маппинг Tailwind-цветов в §1 уже идёт через `var(--color-*)`, а не
|
||||
хардкод, **подавляющему большинству компонентов не нужна отдельная
|
||||
`dark:`-логика** — те же классы (`bg-bg`, `text-ink-900`, `bg-accent`)
|
||||
автоматически перекрашиваются вместе со сменой `data-theme`.
|
||||
- Существующий Tailwind `dark:`-вариант (`darkMode: ['selector', '[data-theme="room"]']`)
|
||||
остаётся зарезервирован **только за комнатой** (см. комментарий в конфиге
|
||||
§1) — не путать с тёмной темой оболочки, у них разный контракт (комната не
|
||||
переключается, оболочка — переключается и по умолчанию следует ОС).
|
||||
- Для редких мест, где нужен именно Tailwind-вариант под тёмную оболочку
|
||||
(например, компонентные исключения §1.2b DESIGN_SYSTEM.md — glass-обводка,
|
||||
focus/error-glow, если их проще выразить `class`, а не `var()`) — завести
|
||||
отдельный кастомный вариант `shell-dark:` через `addVariant`, с тем же
|
||||
двойным условием, что и в `tokens.css`:
|
||||
|
||||
```ts
|
||||
import plugin from 'tailwindcss/plugin'
|
||||
|
||||
export default {
|
||||
// ...
|
||||
plugins: [
|
||||
plugin(({ addVariant }) => {
|
||||
addVariant('shell-dark', [
|
||||
'&[data-theme="dark"]',
|
||||
'@media (prefers-color-scheme: dark) { &:not([data-theme]) }',
|
||||
])
|
||||
}),
|
||||
],
|
||||
} satisfies Config
|
||||
```
|
||||
|
||||
Использование: `shell-dark:border-white/10` и т.п. — только для случаев,
|
||||
которые принципиально нельзя выразить через `var()` (см. категории 3–4 в
|
||||
§1.2b DESIGN_SYSTEM.md); для всех обычных цветов компонентов — `var()`-токены,
|
||||
`shell-dark:` не нужен.
|
||||
|
||||
## 2. shadcn/ui
|
||||
|
||||
`components.json` продолжает использовать CSS-переменные (`cssVariables: true`),
|
||||
но вместо стандартной shadcn-палитры (`--primary`, `--secondary`, …) — алиасить
|
||||
их на токены проекта в `globals.css`:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--primary: var(--color-ink-700);
|
||||
--primary-foreground: var(--color-surface);
|
||||
--secondary: var(--color-bg-alt);
|
||||
--destructive: var(--color-danger);
|
||||
--destructive-foreground: #FFFFFF;
|
||||
--ring: var(--color-focus-ring);
|
||||
--radius: 24px; /* соответствует --radius-2xl, база для shadcn radius-cascade */
|
||||
}
|
||||
|
||||
[data-theme='room'] {
|
||||
--primary: var(--color-room-mic-on);
|
||||
--destructive: var(--color-room-danger-bg);
|
||||
--ring: var(--color-room-focus-ring);
|
||||
}
|
||||
```
|
||||
|
||||
Тёмную тему оболочки (`[data-theme='dark']`) здесь отдельно объявлять не
|
||||
нужно: блок `:root` выше уже целиком построен на `var(--color-*)`, а эти
|
||||
переменные сами переопределяются в `tokens.css` под `[data-theme='dark']`/
|
||||
`prefers-color-scheme` — shadcn-компоненты перекрашиваются вместе с оболочкой
|
||||
автоматически, без дублирования правил.
|
||||
|
||||
`Button` variant `default` переопределить на pill (`rounded-full`) + `bg-accent
|
||||
text-accent-text hover:bg-accent-hover active:bg-accent-active`
|
||||
вместо стандартного `bg-primary`. Variant `outline` — `border-border-pill
|
||||
text-ink-700 rounded-full`.
|
||||
|
||||
## 3. Иконки
|
||||
|
||||
`lucide-react` устанавливается как обычная npm-зависимость (не CDN),
|
||||
`stroke-width={2}` по умолчанию через обёртку `<Icon />` в `frontend/src/components/ui/icon.tsx`,
|
||||
цвет — через `currentColor` (наследуется от текстового токена родителя).
|
||||
Список иконок по экранам — раздел 6 `DESIGN_SYSTEM.md`.
|
||||
|
||||
## 4. Паттерны динамических конференций — для frontend-dev
|
||||
|
||||
Макеты `design/mockups/lobby.html`, `calendar.html`, `my-conferences.html`,
|
||||
`join.html` вводят компоненты, которых нет в статичном списке комнат (см.
|
||||
`DESIGN_SYSTEM.md` §4.12–4.15). При переносе в `frontend/`:
|
||||
|
||||
- **Раскрытие тумблером («Закрытая по паролю», «Закрепить постоянную
|
||||
конференцию») и radio-группа повторений** в HTML-макете сделаны чистым
|
||||
CSS через `:has()` (без JS/зависимостей) — во frontend это обычный
|
||||
**controlled-компонент** (`useState<boolean>` для тумблера,
|
||||
`useState<RecurrenceType>` для radio-группы), `:has()` не переносится.
|
||||
- **Пилюли «ссылка/номер» с копированием** (§4.15) — обёртка над
|
||||
`navigator.clipboard.writeText`, состояние «скопировано» — таймер
|
||||
`setTimeout` ~1.5s (как в статическом макете `my-conferences.html`), плюс
|
||||
`aria-live="polite"` для тултипа «Скопировано» (в макете — визуальный
|
||||
`opacity`-переход, без `aria-live`, добавить во frontend).
|
||||
- **Бейдж статуса конференции** (§4.6) заменяет прежний `Badge` для комнат:
|
||||
варианты `scheduled` / `pinned` / `live` / `ended` / `cancelled`; закрытость
|
||||
паролем — отдельная иконка `Lock` рядом с заголовком, не вариант `Badge`.
|
||||
- **Карточки-действия хаба** (§4.12) — фиксированная высота (не `min-height`)
|
||||
обязательна для симметрии сетки 2×2; в CSS-grid Tailwind — `grid-rows-2`
|
||||
с явной высотой строки (`grid-template-rows: repeat(2, 340px)` через
|
||||
`[grid-template-rows:repeat(2,340px)]` или отдельный `h-[340px]` на карточке).
|
||||
348
design/tokens.css
Normal file
348
design/tokens.css
Normal file
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* VidConf — design tokens
|
||||
* Источник истины для design/DESIGN_SYSTEM.md. Значения синхронизированы 1:1.
|
||||
*
|
||||
* Три темы, независимые механизмы переключения:
|
||||
* - Оболочка, светлая (дефолт) — :root.
|
||||
* - Оболочка, тёмная — :root[data-theme="dark"] (переключатель
|
||||
* пользователя) ИЛИ автоматически по @media (prefers-color-scheme: dark),
|
||||
* если data-theme не проставлен явно (см. блок ниже). Утверждено
|
||||
* 2026-07-18 (фаза 6a, design/mockups/dark/README.md) — палитра 1:1 из
|
||||
* утверждённых тёмных макетов оболочки.
|
||||
* - Комната конференции — [data-theme="room"] (скоуп на контейнер
|
||||
* экрана конференции; отдельный неймспейс --color-room-*, НЕ переключается
|
||||
* системной темой и НЕ смешивается с dark-оболочкой — комната всегда
|
||||
* тёмная независимо от выбора пользователя).
|
||||
*
|
||||
* Шрифты. Семейства и веса ниже — контракт дизайн-системы (Unbounded —
|
||||
* display, Inter — body, JetBrains Mono — mono) и синхронизированы 1:1 с
|
||||
* DESIGN_SYSTEM.md. Реализация подключения различается по среде, БЕЗ внешних
|
||||
* CDN (инвариант приватности):
|
||||
* - Собранный фронт (frontend/) — шрифты самохостятся через npm-пакеты
|
||||
* @fontsource с кириллическим subset (импорты в frontend/src/main.tsx);
|
||||
* там ручных @font-face нет.
|
||||
* - Статические HTML-макеты (design/mockups/) — сборки npm нет, поэтому
|
||||
* подключаются @font-face ниже. При отсутствии локальных woff2 браузер
|
||||
* молча уходит в fallback-стек без сетевых запросов (макеты остаются
|
||||
* рабочими; кастомная типографика появляется, если положить woff2 рядом).
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'Unbounded';
|
||||
src: url('../fonts/Unbounded/Unbounded-Regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Unbounded';
|
||||
src: url('../fonts/Unbounded/Unbounded-SemiBold.woff2') format('woff2');
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Unbounded';
|
||||
src: url('../fonts/Unbounded/Unbounded-Bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('../fonts/Inter/Inter-Variable.woff2') format('woff2-variations');
|
||||
font-weight: 400 800;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
src: url('../fonts/JetBrainsMono/JetBrainsMono-Medium.woff2') format('woff2');
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: light;
|
||||
|
||||
/* ---------- Типографика ---------- */
|
||||
--font-display: 'Unbounded', 'Arial Rounded MT Bold', -apple-system, 'Segoe UI', sans-serif;
|
||||
--font-body: 'Inter', -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'SFMono-Regular', Menlo, Consolas, monospace;
|
||||
|
||||
--text-display-2xl: 700 64px/1.05 var(--font-display);
|
||||
--text-display-xl: 700 48px/1.1 var(--font-display);
|
||||
--text-display-lg: 600 34px/1.2 var(--font-display);
|
||||
--text-h1: 700 28px/1.25 var(--font-body);
|
||||
--text-h2: 600 22px/1.3 var(--font-body);
|
||||
--text-h3: 600 18px/1.35 var(--font-body);
|
||||
--text-body-lg: 400 16px/1.5 var(--font-body);
|
||||
--text-body: 400 14px/1.5 var(--font-body);
|
||||
--text-caption: 600 12px/1.4 var(--font-body);
|
||||
--text-mono-sm: 500 13px/1.4 var(--font-mono);
|
||||
|
||||
/* ---------- Spacing (8pt grid, база 4px) ---------- */
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-10: 40px;
|
||||
--space-12: 48px;
|
||||
--space-16: 64px;
|
||||
--space-20: 80px;
|
||||
--space-24: 96px;
|
||||
|
||||
/* ---------- Радиусы ---------- */
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 16px;
|
||||
--radius-xl: 20px;
|
||||
--radius-2xl: 24px;
|
||||
--radius-3xl: 32px;
|
||||
--radius-full: 999px;
|
||||
|
||||
/* ---------- Границы ---------- */
|
||||
--border-thin: 1px;
|
||||
--border-md: 1.5px;
|
||||
--border-thick: 2px;
|
||||
--border-speaker: 3px;
|
||||
|
||||
/* ---------- Breakpoints (справочно, desktop-first) ---------- */
|
||||
--bp-desktop: 1440px;
|
||||
--bp-min: 1280px;
|
||||
|
||||
/* ---------- Motion ---------- */
|
||||
--transition-fast: 120ms ease;
|
||||
--transition-base: 200ms ease;
|
||||
|
||||
/* ---------- Цвета: светлая тема (оболочка) ---------- */
|
||||
--color-bg: #F1F1F1;
|
||||
--color-bg-alt: #F7F7F8;
|
||||
--color-surface: #FFFFFF;
|
||||
--color-surface-glass: rgba(255, 255, 255, 0.55);
|
||||
|
||||
--color-border: #E1E3E9;
|
||||
--color-border-strong: #C7CCDC;
|
||||
--color-border-pill: #4A5A9E;
|
||||
|
||||
--color-ink-900: #2E3454;
|
||||
--color-ink-700: #3B4D95;
|
||||
--color-ink-500: #535F94;
|
||||
--color-ink-400: #6976AC;
|
||||
--color-ink-300: #A6AECB;
|
||||
|
||||
/* Пастельная мята — фаза R1 (пивот, 2026-07-16): переименовано из
|
||||
--color-accent-green*, акцент больше не обязан быть зелёным.
|
||||
Осветлено на 50% (смешение 50/50 с белым) по фидбеку гейта утверждения
|
||||
R1-итерации (2026-07-16): #A9E4C6→#D4F2E3, #8FD6B2→#C7EBD9,
|
||||
#74C89D→#BAE4CE. Mapping см. design/DESIGN_SYSTEM.md §1.1/§1.3. */
|
||||
--color-accent: #D4F2E3;
|
||||
--color-accent-hover: #C7EBD9;
|
||||
--color-accent-active: #BAE4CE;
|
||||
--color-accent-text: #2E3454;
|
||||
|
||||
--color-success: #0A7A4C;
|
||||
--color-success-bg: #E4F7ED;
|
||||
--color-success-dot: #46916B;
|
||||
|
||||
--color-danger: #C7364F;
|
||||
--color-danger-bg: #FCE7EB;
|
||||
--color-danger-dot: #C65366;
|
||||
|
||||
--color-warning: #7F560D;
|
||||
--color-warning-bg: #FBF0DC;
|
||||
--color-warning-dot: #B58A26;
|
||||
|
||||
--color-focus-ring: #3B4D95;
|
||||
|
||||
/* ---------- Тени (светлая тема) ---------- */
|
||||
--shadow-sm: 0 1px 2px rgba(46, 52, 84, 0.06);
|
||||
--shadow-md: 0 4px 16px rgba(46, 52, 84, 0.08);
|
||||
--shadow-lg: 0 12px 32px rgba(46, 52, 84, 0.12);
|
||||
--shadow-glass: 0 8px 24px rgba(59, 77, 149, 0.15);
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
* Тёмная тема: оболочка (auth/лобби/календарь/join/«мои конференции»/админка)
|
||||
* Утверждено 2026-07-18 (фаза 6a). Палитра — 1:1 копия блока :root из
|
||||
* design/mockups/dark/*.html (все 6 файлов идентичны), происхождение каждого
|
||||
* значения задокументировано в design/mockups/dark/README.md и продублировано
|
||||
* в design/DESIGN_SYSTEM.md §1.2b. Принцип: тёмная оболочка не изобретает
|
||||
* новых цветов, а переиспользует уже утверждённые --color-room-* в новых
|
||||
* ролях (см. таблицу происхождения), поэтому тёмное лобби и тёмная комната
|
||||
* читаются как одна система.
|
||||
*
|
||||
* Механизм активации (два независимых триггера, одни и те же переопределения
|
||||
* --color-* / --shadow-* — те же имена, что в :root светлой темы выше):
|
||||
* 1) явный выбор пользователя — атрибут data-theme="dark" на <html>/:root;
|
||||
* 2) дефолт по системной теме — @media (prefers-color-scheme: dark),
|
||||
* только пока data-theme НЕ проставлен явно (:root:not([data-theme])).
|
||||
* Явный data-theme="light" отключает автодетект и форсирует светлую
|
||||
* тему (базовый :root выше) даже при тёмной ОС.
|
||||
* Оба блока продублированы намеренно (plain CSS, без препроцессора) — при
|
||||
* правке палитры менять оба синхронно.
|
||||
* ===================================================================== */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme]) {
|
||||
color-scheme: dark;
|
||||
|
||||
--color-bg: #1E1E1E;
|
||||
--color-bg-alt: #242424;
|
||||
--color-surface: #2A2A2A;
|
||||
--color-surface-glass: rgba(51, 51, 51, 0.55);
|
||||
|
||||
--color-border: #3F3F3F;
|
||||
--color-border-strong: #585858;
|
||||
--color-border-pill: #7FDDA8;
|
||||
|
||||
--color-ink-900: #E8E8E8;
|
||||
--color-ink-700: #7FDDA8;
|
||||
--color-ink-500: #B0B0B0;
|
||||
--color-ink-400: #5D9A78;
|
||||
--color-ink-300: #7A7A7A;
|
||||
|
||||
--color-accent: #7FDDA8;
|
||||
--color-accent-hover: #75CA9A;
|
||||
--color-accent-active: #6CB78C;
|
||||
--color-accent-text: #10331F;
|
||||
|
||||
--color-success: #7FDDA8;
|
||||
--color-success-bg: rgba(127, 221, 168, 0.14);
|
||||
--color-success-dot: #7FDDA8;
|
||||
|
||||
--color-danger: #EB93A1;
|
||||
--color-danger-bg: rgba(235, 147, 161, 0.14);
|
||||
--color-danger-dot: #EB93A1;
|
||||
--color-danger-solid: #B85468;
|
||||
--color-danger-solid-hover: #B9495F;
|
||||
|
||||
--color-warning: #D6A83D;
|
||||
--color-warning-bg: rgba(214, 168, 61, 0.16);
|
||||
--color-warning-dot: #D6A83D;
|
||||
|
||||
--color-focus-ring: #D6A83D;
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
|
||||
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5);
|
||||
--shadow-glass: 0 8px 24px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] {
|
||||
color-scheme: dark;
|
||||
|
||||
--color-bg: #1E1E1E;
|
||||
--color-bg-alt: #242424;
|
||||
--color-surface: #2A2A2A;
|
||||
--color-surface-glass: rgba(51, 51, 51, 0.55);
|
||||
|
||||
--color-border: #3F3F3F;
|
||||
--color-border-strong: #585858;
|
||||
--color-border-pill: #7FDDA8;
|
||||
|
||||
--color-ink-900: #E8E8E8;
|
||||
--color-ink-700: #7FDDA8;
|
||||
--color-ink-500: #B0B0B0;
|
||||
--color-ink-400: #5D9A78;
|
||||
--color-ink-300: #7A7A7A;
|
||||
|
||||
--color-accent: #7FDDA8;
|
||||
--color-accent-hover: #75CA9A;
|
||||
--color-accent-active: #6CB78C;
|
||||
--color-accent-text: #10331F;
|
||||
|
||||
--color-success: #7FDDA8;
|
||||
--color-success-bg: rgba(127, 221, 168, 0.14);
|
||||
--color-success-dot: #7FDDA8;
|
||||
|
||||
--color-danger: #EB93A1;
|
||||
--color-danger-bg: rgba(235, 147, 161, 0.14);
|
||||
--color-danger-dot: #EB93A1;
|
||||
--color-danger-solid: #B85468;
|
||||
--color-danger-solid-hover: #B9495F;
|
||||
|
||||
--color-warning: #D6A83D;
|
||||
--color-warning-bg: rgba(214, 168, 61, 0.16);
|
||||
--color-warning-dot: #D6A83D;
|
||||
|
||||
--color-focus-ring: #D6A83D;
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
|
||||
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5);
|
||||
--shadow-glass: 0 8px 24px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
/* ---------- Тёмная тема: комната конференции ---------- */
|
||||
[data-theme='room'] {
|
||||
color-scheme: dark;
|
||||
|
||||
--color-room-bg: #1E1E1E;
|
||||
--color-room-surface: #2A2A2A;
|
||||
--color-room-surface-raised: #333333;
|
||||
|
||||
--color-room-tile: #363636;
|
||||
--color-room-tile-border: #3F3F3F;
|
||||
--color-room-tile-hover: #3D3D3D;
|
||||
|
||||
--color-room-text-primary: #E8E8E8;
|
||||
--color-room-text-secondary: #B0B0B0;
|
||||
--color-room-text-tertiary: #7A7A7A;
|
||||
|
||||
--color-room-mic-on: #7FDDA8;
|
||||
--color-room-mic-off: #EB93A1;
|
||||
--color-room-speaker-ring: #D6A83D;
|
||||
|
||||
--color-room-danger: #EB93A1;
|
||||
--color-room-danger-bg: #B85468;
|
||||
--color-room-danger-bg-hover: #B9495F;
|
||||
|
||||
--color-room-focus-ring: #D6A83D;
|
||||
|
||||
--shadow-speaker-glow: 0 0 0 3px var(--color-room-speaker-ring), 0 0 24px rgba(214, 168, 61, 0.35);
|
||||
--shadow-room-panel: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* ---------- Базовые сбросы, общие для макетов ---------- */
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font: var(--text-body-lg);
|
||||
color: var(--color-ink-900);
|
||||
background: var(--color-bg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
[data-theme='room'] body,
|
||||
[data-theme='room'] {
|
||||
background: var(--color-room-bg);
|
||||
color: var(--color-room-text-primary);
|
||||
}
|
||||
|
||||
a { color: inherit; }
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-focus-ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
[data-theme='room'] :focus-visible {
|
||||
outline-color: var(--color-room-focus-ring);
|
||||
}
|
||||
|
||||
svg.icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
stroke: currentColor;
|
||||
stroke-width: 2;
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
164
design/tools/contrast.py
Normal file
164
design/tools/contrast.py
Normal file
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Проверка контраста токенов VidConf по WCAG 2.1 (относительная светимость sRGB).
|
||||
|
||||
Источник значений — design/tokens.css (продублированы явно ниже, без парсинга
|
||||
CSS, чтобы список пар был читаемым и явным). Покрывает все три темы проекта:
|
||||
светлую оболочку (дефолт), тёмную оболочку ([data-theme='dark'] /
|
||||
prefers-color-scheme, фаза 6a) и комнату конференции ([data-theme='room']).
|
||||
|
||||
Пороги:
|
||||
- «текст» (text) — обычный текст, надписи на кнопках/бейджах: >= 4.5:1
|
||||
- «нетекст» (non-text) — иконки-индикаторы, точки статусов, декоративные
|
||||
рамки/обводки, несущие смысл без текста (правило WCAG 1.4.11): >= 3:1
|
||||
|
||||
Запуск: `python3 design/tools/contrast.py`
|
||||
Печатает таблицу пар с ratio и PASS/FAIL, exit code 1 при хотя бы одном провале.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
def _linearize(channel: float) -> float:
|
||||
c = channel / 255.0
|
||||
return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
|
||||
|
||||
|
||||
def relative_luminance(hex_color: str) -> float:
|
||||
"""Относительная светимость sRGB-цвета (формула WCAG 2.1)."""
|
||||
h = hex_color.lstrip("#")
|
||||
r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
|
||||
r_lin, g_lin, b_lin = _linearize(r), _linearize(g), _linearize(b)
|
||||
return 0.2126 * r_lin + 0.7152 * g_lin + 0.0722 * b_lin
|
||||
|
||||
|
||||
def contrast_ratio(hex_a: str, hex_b: str) -> float:
|
||||
"""Контраст пары цветов по WCAG: (L_light + 0.05) / (L_dark + 0.05)."""
|
||||
l_a, l_b = relative_luminance(hex_a), relative_luminance(hex_b)
|
||||
l_light, l_dark = max(l_a, l_b), min(l_a, l_b)
|
||||
return (l_light + 0.05) / (l_dark + 0.05)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Pair:
|
||||
name: str
|
||||
fg: str
|
||||
bg: str
|
||||
kind: str # "текст" | "нетекст"
|
||||
threshold: float
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Явный список пар. Значения продублированы из design/tokens.css.
|
||||
# ---------------------------------------------------------------------------
|
||||
PAIRS: list[Pair] = [
|
||||
# ---- Светлая тема (оболочка): CTA-кнопка (пастельная мята, осветлена
|
||||
# на 50% по фидбеку гейта утверждения R1-итерации, 2026-07-16) ----
|
||||
Pair("CTA primary: текст на заливке default", "#2E3454", "#D4F2E3", "текст", 4.5),
|
||||
Pair("CTA primary: текст на заливке hover", "#2E3454", "#C7EBD9", "текст", 4.5),
|
||||
Pair("CTA primary: текст на заливке active", "#2E3454", "#BAE4CE", "текст", 4.5),
|
||||
# ---- Светлая тема: точки-индикаторы статуса (лобби, фильтр-чипы) ----
|
||||
Pair("Точка статуса «свободна» (success-dot) на карточке", "#46916B", "#FFFFFF", "нетекст", 3.0),
|
||||
Pair("Точка статуса «занята» (danger-dot) на карточке", "#C65366", "#FFFFFF", "нетекст", 3.0),
|
||||
Pair("Точка статуса «забронирована» (warning-dot) на карточке", "#B58A26", "#FFFFFF", "нетекст", 3.0),
|
||||
# ---- Светлая тема: регрессия — текст статусов на белой поверхности
|
||||
# (та же база измерения, что и в DESIGN_SYSTEM.md §1.1: «на белом») ----
|
||||
Pair("Текст статуса «свободна» на белой поверхности", "#0A7A4C", "#FFFFFF", "текст", 4.5),
|
||||
Pair("Текст статуса «занята» на белой поверхности", "#C7364F", "#FFFFFF", "текст", 4.5),
|
||||
Pair("Текст статуса «забронирована» на белой поверхности", "#7F560D", "#FFFFFF", "текст", 4.5),
|
||||
# ---- Светлая тема: регрессия — базовая типографика (не менялась) ----
|
||||
Pair("Основной текст (ink-900) на альт.-фоне", "#2E3454", "#F7F7F8", "текст", 4.5),
|
||||
Pair("Брендовый индиго (ink-700) на фоне страницы", "#3B4D95", "#F1F1F1", "текст", 4.5),
|
||||
# ---- Логотип «объектив» (§1.4 DESIGN_SYSTEM.md) — монохром ink-700,
|
||||
# проверяем как графику (нетекст, >=3:1) на обеих светлых поверхностях ----
|
||||
Pair("Логотип «объектив» (ink-700) на фоне страницы", "#3B4D95", "#F1F1F1", "нетекст", 3.0),
|
||||
Pair("Логотип «объектив» (ink-700) на белой поверхности", "#3B4D95", "#FFFFFF", "нетекст", 3.0),
|
||||
# ---- Аватары с инициалами (§1.5 DESIGN_SYSTEM.md) — текст всегда
|
||||
# светлый на индиго-заливке, никогда тёмный (регрессия гейта, 2026-07-16) ----
|
||||
Pair("Инициалы аватара (белые) на заливке ink-700 (основной)", "#FFFFFF", "#3B4D95", "текст", 4.5),
|
||||
Pair("Инициалы аватара (белые) на заливке ink-500 (второстепенный)", "#FFFFFF", "#535F94", "текст", 4.5),
|
||||
# ---- Тёмная тема (комната): микрофон вкл/выкл — пастельные индикаторы ----
|
||||
Pair("Микрофон «вкл» (иконка) на тулбаре/панели", "#7FDDA8", "#2A2A2A", "нетекст", 3.0),
|
||||
Pair("Микрофон «вкл» (иконка) на плитке участника", "#7FDDA8", "#363636", "нетекст", 3.0),
|
||||
Pair("Микрофон «выкл/mute» (иконка) на тулбаре/панели", "#EB93A1", "#2A2A2A", "нетекст", 3.0),
|
||||
Pair("Микрофон «выкл/mute» (иконка) на плитке участника", "#EB93A1", "#363636", "нетекст", 3.0),
|
||||
# ---- Тёмная тема: кнопка «Выйти» (заливка danger, белый текст) ----
|
||||
Pair("Кнопка «Выйти»: белый текст на заливке default", "#FFFFFF", "#B85468", "текст", 4.5),
|
||||
Pair("Кнопка «Выйти»: белый текст на заливке hover", "#FFFFFF", "#B9495F", "текст", 4.5),
|
||||
# ---- Тёмная тема: регрессия — текст на плитке участника (не менялся) ----
|
||||
Pair("Имя участника (текст) на плитке", "#E8E8E8", "#363636", "текст", 4.5),
|
||||
Pair("Вторичный текст (время/статус) на плитке", "#B0B0B0", "#363636", "текст", 4.5),
|
||||
# ---- Тёмная тема: рамка активного спикера (декоративная, крупный элемент) ----
|
||||
Pair("Рамка активного спикера (speaker-ring) на поверхности", "#D6A83D", "#2A2A2A", "нетекст", 3.0),
|
||||
|
||||
# ---- Тёмная тема оболочки (фаза 6a, 2026-07-18, design/mockups/dark/) ----
|
||||
# CTA-кнопка: бренд-мята переиспользована из --color-room-mic-on,
|
||||
# текст — accent-text (тёмный ink, буквальный hex из room.html) ----
|
||||
Pair("Тёмная оболочка — CTA primary: текст на заливке default", "#10331F", "#7FDDA8", "текст", 4.5),
|
||||
Pair("Тёмная оболочка — CTA primary: текст на заливке hover", "#10331F", "#75CA9A", "текст", 4.5),
|
||||
Pair("Тёмная оболочка — CTA primary: текст на заливке active", "#10331F", "#6CB78C", "текст", 4.5),
|
||||
# ---- Тёмная оболочка: базовая типографика ----
|
||||
Pair("Тёмная оболочка — основной текст (ink-900) на фоне страницы", "#E8E8E8", "#1E1E1E", "текст", 4.5),
|
||||
Pair("Тёмная оболочка — основной текст (ink-900) на поверхности (карточки)", "#E8E8E8", "#2A2A2A", "текст", 4.5),
|
||||
Pair("Тёмная оболочка — вторичный текст (ink-500) на альт.-фоне", "#B0B0B0", "#242424", "текст", 4.5),
|
||||
# ---- Тёмная оболочка: брендовая мята (ink-700 = accent = --color-room-mic-on) ----
|
||||
Pair("Тёмная оболочка — бренд-мята (ink-700, заголовки/ссылки) на фоне страницы", "#7FDDA8", "#1E1E1E", "текст", 4.5),
|
||||
Pair("Тёмная оболочка — бренд-мята (ink-700) на поверхности", "#7FDDA8", "#2A2A2A", "текст", 4.5),
|
||||
# ---- Тёмная оболочка: текст статусов на поверхности (карточки конференций) ----
|
||||
Pair("Тёмная оболочка — текст статуса «занята»/ошибка (danger) на поверхности", "#EB93A1", "#2A2A2A", "текст", 4.5),
|
||||
Pair("Тёмная оболочка — текст статуса «забронирована» (warning) на поверхности", "#D6A83D", "#2A2A2A", "текст", 4.5),
|
||||
# ---- Тёмная оболочка: сплошные danger-кнопки (.btn-danger — «Отменить
|
||||
# все повторения» в календаре, «Удалить» в админке); отдельный токен
|
||||
# --color-danger-solid(-hover), см. DESIGN_SYSTEM.md §1.2b ----
|
||||
Pair("Тёмная оболочка — btn-danger: белый текст на заливке default", "#FFFFFF", "#B85468", "текст", 4.5),
|
||||
Pair("Тёмная оболочка — btn-danger: белый текст на заливке hover", "#FFFFFF", "#B9495F", "текст", 4.5),
|
||||
# ---- Тёмная оболочка: аватар (патч категории 1, README dark/) — белый
|
||||
# текст на мятной заливке нечитаем, заменён на accent-text ----
|
||||
Pair("Тёмная оболочка — инициалы аватара (accent-text) на заливке ink-700", "#10331F", "#7FDDA8", "текст", 4.5),
|
||||
# ---- Тёмная оболочка: third-tier текст (ink-400, эйбрау-подписи в
|
||||
# [скобках]) — только крупный/жирный текст, аналог light ink-400 ⚠︎ ----
|
||||
Pair("Тёмная оболочка — third-tier (ink-400, ⚠︎ крупный текст) на фоне страницы", "#5D9A78", "#1E1E1E", "нетекст", 3.0),
|
||||
Pair("Тёмная оболочка — third-tier (ink-400, ⚠︎ крупный текст) на поверхности", "#5D9A78", "#2A2A2A", "нетекст", 3.0),
|
||||
# ---- Тёмная оболочка: кольцо :focus-visible (переиспользован --color-room-focus-ring) ----
|
||||
Pair("Тёмная оболочка — кольцо :focus-visible на поверхности", "#D6A83D", "#2A2A2A", "нетекст", 3.0),
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
rows = []
|
||||
all_pass = True
|
||||
for pair in PAIRS:
|
||||
ratio = contrast_ratio(pair.fg, pair.bg)
|
||||
ok = ratio >= pair.threshold
|
||||
all_pass = all_pass and ok
|
||||
rows.append((pair, ratio, ok))
|
||||
|
||||
name_width = max(len(p.name) for p, _, _ in rows)
|
||||
header = (
|
||||
f"{'Пара':<{name_width}} {'fg':>7} {'bg':>7} {'Тип':<8} "
|
||||
f"{'Порог':>6} {'Ratio':>7} Итог"
|
||||
)
|
||||
print(header)
|
||||
print("-" * len(header))
|
||||
for pair, ratio, ok in rows:
|
||||
status = "PASS" if ok else "FAIL"
|
||||
print(
|
||||
f"{pair.name:<{name_width}} {pair.fg:>7} {pair.bg:>7} "
|
||||
f"{pair.kind:<8} {pair.threshold:>5.1f}:1 {ratio:>6.2f}:1 {status}"
|
||||
)
|
||||
print("-" * len(header))
|
||||
|
||||
passed = sum(1 for _, _, ok in rows if ok)
|
||||
print(f"Итого: {passed}/{len(rows)} пар прошли проверку.")
|
||||
|
||||
if not all_pass:
|
||||
print("ПРОВАЛ: есть пары с контрастом ниже порога WCAG.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print("OK: все пары соответствуют порогам WCAG 2.1.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user