415 lines
22 KiB
HTML
415 lines
22 KiB
HTML
<!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>
|