diff --git a/docs/decisions/024-identity-and-ui.md b/docs/decisions/024-identity-and-ui.md index 9d871e0..837a537 100644 --- a/docs/decisions/024-identity-and-ui.md +++ b/docs/decisions/024-identity-and-ui.md @@ -1,6 +1,6 @@ # ADR-024: Айдентика «Скобы», интерфейс и язык -Уточнён [ADR-075](075-no-zoom-app-feel.md) (масштабирование запрещено, размеры шрифтов прежние), [ADR-067](067-version-in-sidebar-foot.md) (версия и время коммита в подвале сайдбара), [ADR-076](076-ios-input-accessory-is-system.md) (строка сообщения — `textarea`) и [ADR-077](077-message-alignment-by-pointer.md) (стороны сообщений зависят от способа ввода). +Уточнён [ADR-075](075-no-zoom-app-feel.md) (масштабирование запрещено, размеры шрифтов прежние), [ADR-067](067-version-in-sidebar-foot.md) (версия и время коммита в подвале сайдбара), [ADR-076](076-ios-input-accessory-is-system.md) (строка сообщения — `textarea`), [ADR-077](077-message-alignment-by-pointer.md) (стороны сообщений зависят от способа ввода) и [ADR-079](079-profile-glyph-and-settings-layout.md) (фиксированная пиктограмма профиля и компоновка настроек). ## Контекст diff --git a/docs/decisions/067-version-in-sidebar-foot.md b/docs/decisions/067-version-in-sidebar-foot.md index 59b2bfd..4abfa32 100644 --- a/docs/decisions/067-version-in-sidebar-foot.md +++ b/docs/decisions/067-version-in-sidebar-foot.md @@ -1,5 +1,7 @@ # ADR-067: Версия и время коммита в подвале сайдбара +Уточнён [ADR-079](079-profile-glyph-and-settings-layout.md): кнопка «ты: @nick» стала локальной пиктограммой профиля с `@nick`; версия осталась отдельной строкой рядом. + Доводит до интерфейса [ADR-074](074-version-and-commit-time.md): сервер отдаёт `version` и `commitAt`, а где их показывать, там не решено. ## Контекст diff --git a/docs/decisions/079-profile-glyph-and-settings-layout.md b/docs/decisions/079-profile-glyph-and-settings-layout.md new file mode 100644 index 0000000..555e881 --- /dev/null +++ b/docs/decisions/079-profile-glyph-and-settings-layout.md @@ -0,0 +1,24 @@ +# ADR-079: Пиктограмма профиля и компоновка настроек + +Уточняет [ADR-024](024-identity-and-ui.md): аватаров по-прежнему нет, но переходу в настройки нужна однозначная пиктограмма. Уточняет [ADR-067](067-version-in-sidebar-foot.md): состав кнопки профиля меняется, версия остаётся рядом с ней. + +## Контекст + +В подвале сайдбара переход в настройки обозначен точкой и текстом «ты: @nick». Точка не объясняет действие, а слово «ты» занимает место у ника. Использовать эмодзи нельзя: его вид зависит от платформы. Подключать библиотеку ради одной пиктограммы противоречит устройству клиента. + +Экран настроек ограничен одной колонкой 460 px и на десктопе остаётся длинной узкой лентой. Формы внутри неё дополнительно ограничены 360 px, поэтому поля смены пароля и удаления аккаунта короче соседних кнопок. + +## Решение + +- Кнопка настроек в подвале состоит из локального `profile.svg` и `@nick`. Её доступное имя — «настройки: @nick». +- Пиктограмма фиксированная, не пользовательская: это указатель на профиль, а не аватар. Круглая голова делает назначение узнаваемым, угловатые открытые плечи, viewBox 64, один штрих 7 px и цвет `ink` связывают её со «Скобами». Человека внутрь фирменного знака не помещаем. +- Ассет хранится в клиенте и входит в офлайн-оболочку. Зависимостей и сборки не появляется. +- До ширины 959 px настройки идут одной колонкой в прежнем порядке. От 960 px блок идентичности занимает всю ширину, ниже стоят две равные колонки с промежутком 32 px: уведомления, установка, устройства и история слева; смена пароля, выход и удаление аккаунта справа. +- Ширина двух колонок вместе — не больше 752 px. Поля и обычные кнопки занимают полную ширину своей колонки и сохраняют общую минимальную высоту 44 px. +- Новая компоновка относится только к `#/settings`. Карточка контакта и участники, использующие тот же базовый класс узкого содержимого, не меняются. + +## Следствия + +- В подвале остаются только пиктограмма, ник и независимая строка версии; длинный ник по-прежнему обрезается многоточием. +- На телефоне порядок и одноколоночная компоновка настроек сохраняются. На широком десктопе путь чтения делится на повседневные настройки слева и действия с доступом к аккаунту справа. +- Смена `app.css`, модулей и состава оболочки требует новой версии service worker. diff --git a/docs/identity/brief.md b/docs/identity/brief.md index 27162aa..1c9529b 100644 --- a/docs/identity/brief.md +++ b/docs/identity/brief.md @@ -10,6 +10,8 @@ Wordmark — слово `bare` строчными рядом со знаком, тем же шрифтом, что интерфейс. «Bare» с заглавной — только в тексте. +Пиктограмма профиля — не вариант знака и не аватар. Это отдельный фиксированный `profile.svg`: круглая голова и открытые угловатые плечи, тот же viewBox 64, один цвет `ink` и штрих 7. Внутрь фирменного знака человека не помещать (ADR-079). + ## Цвет | имя | значение | роль | @@ -42,6 +44,7 @@ font-family: ui-monospace, "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monos ## Компоновка - Десктоп: сайдбар 224 px с правой границей `line`, шапка 64 px, отступы контента 32 px. +- Настройки: одна колонка до 959 px; от 960 px идентичность во всю ширину и две равные колонки шириной до 360 px с промежутком 32 px. Поля и кнопки внутри колонок одной ширины (ADR-079). - Мобильный: шапка 56 px, отступы 20 px, ввод с min-height 44 px. Нижнее поле подвала и строки ввода считается с `env(safe-area-inset-bottom)`: под системную полосу iPhone они не заходят. При открытой экранной клавиатуре её прямоугольник уже защищает край, поэтому обычные `20px + safe-area` строки ввода заменяются компактными 6 px (ADR-076). - Сообщения — поток, автор и время над текстом; на телефоне и планшете свои — у правого края, чужие — у левого, на десктопе все — у левого; ширина блока не больше `min(640px, 85%)` (ADR-077). - Ввод — рамка 1 px ink, без скруглений, `>` цветом mark слева. diff --git a/docs/open-questions.md b/docs/open-questions.md index 4923d7a..5f55a40 100644 --- a/docs/open-questions.md +++ b/docs/open-questions.md @@ -5,4 +5,4 @@ - Блокировка собеседника и персональные инвайты — если общего инвайт-кода и лимитов (ADR-019, ADR-021) окажется мало. - Подписи сообщений вторым ключом — если потребуется защита от сговора участника комнаты с сервером (ADR-016). -Закрыто ADR-015…024: регистрация, контакты, лимиты, смена пароля, идентификация устройств, язык интерфейса, айдентика, доверие к ключам, протокол, схема базы, деплой, правила пушей. ADR-064 и ADR-069 — попытки убрать системную панель ввода iOS, ADR-065 — сторона сообщений в ленте, ADR-066 — серверный «перец», ADR-067…075 — версия в подвале, самообновление PWA, строка ввода, запрет масштабирования, код причины отказа пушей. ADR-076 фиксирует системное ограничение панели iOS и возврат к `textarea`, ADR-077 — стороны сообщений отдельно для touch и десктопа, ADR-078 — выбор между личным чатом и новой комнатой. +Закрыто ADR-015…024: регистрация, контакты, лимиты, смена пароля, идентификация устройств, язык интерфейса, айдентика, доверие к ключам, протокол, схема базы, деплой, правила пушей. ADR-064 и ADR-069 — попытки убрать системную панель ввода iOS, ADR-065 — сторона сообщений в ленте, ADR-066 — серверный «перец», ADR-067…075 — версия в подвале, самообновление PWA, строка ввода, запрет масштабирования, код причины отказа пушей. ADR-076 фиксирует системное ограничение панели iOS и возврат к `textarea`, ADR-077 — стороны сообщений отдельно для touch и десктопа, ADR-078 — выбор между личным чатом и новой комнатой, ADR-079 — пиктограмма профиля и двухколоночные настройки. diff --git a/docs/ui.md b/docs/ui.md index 0284308..15564b8 100644 --- a/docs/ui.md +++ b/docs/ui.md @@ -24,9 +24,9 @@ ## Список чатов (сайдбар) -Секции «каналы» и «личные», как в моке. Активный чат — инверсия (ink на bone). Непрочитанные — число цветом `mark` справа. Порядок — по `lastId` по убыванию. Внизу — «ты: @nick», по нажатию — настройки. Над секциями — строка `+ новый чат`. +Секции «каналы» и «личные», как в моке. Активный чат — инверсия (ink на bone). Непрочитанные — число цветом `mark` справа. Порядок — по `lastId` по убыванию. Внизу — локальная пиктограмма профиля и `@nick`, по нажатию — настройки. Пиктограмма фиксированная и не является аватаром (ADR-079). Над секциями — строка `+ новый чат`. -В подвале рядом с «ты: @nick» — версия и время коммита из `GET /api/config`: `версия · дд.мм чч:мм`, время в местной зоне, цветом `stone`, размером подписи. Не кнопка, но выделяется: её копируют. Времени нет — остаётся версия; конфигурации ещё нет — строки нет. В сайдбаре 224 px рядом они не помещаются: версия занимает вторую строку и стоит справа; на мобильном, где сайдбар во всю ширину, обе стоят в одной строке. Ник, которому не хватило места, обрезается многоточием (ADR-067). +В подвале рядом с кнопкой профиля — версия и время коммита из `GET /api/config`: `версия · дд.мм чч:мм`, время в местной зоне, цветом `stone`, размером подписи. Не кнопка, но выделяется: её копируют. Времени нет — остаётся версия; конфигурации ещё нет — строки нет. В сайдбаре 224 px рядом они не помещаются: версия занимает вторую строку и стоит справа; на мобильном, где сайдбар во всю ширину, обе стоят в одной строке. Ник, которому не хватило места, обрезается многоточием (ADR-067, ADR-079). Версия — серверная. Оболочка в браузере бывает старше: она приезжает из кэша, а обновление применяется отдельно и ждёт пустых полей (ADR-068). @@ -70,6 +70,8 @@ ## Настройки (`#/settings`) +До 959 px разделы идут одной колонкой. От 960 px блок с собственной идентичностью занимает всю ширину, а остальные разделы делятся на две равные колонки: уведомления, установка, устройства и история слева; смена пароля, выход и удаление аккаунта справа. Поля и обычные кнопки занимают полную ширину колонки и имеют одинаковую минимальную высоту 44 px (ADR-079). + - «ты: @nick», свой отпечаток. - «уведомления»: состояние (`включены` / `выключены` / `запрещены в браузере`), кнопка «включить» или «выключить». `запрещены в браузере` — разрешение отклонено или уведомлений в браузере нет вовсе; кнопки в этом состоянии нет (ADR-046). На iOS вне PWA — состояние `выключены` и вместо кнопки текст про установку, тот же, что в баннере. - «установить приложение»: кнопка «установить», если есть `beforeinstallprompt`; на iOS вне PWA — инструкция «поделиться → на экран «домой»». Устанавливать нечего — раздела нет. diff --git a/web/app.css b/web/app.css index 9d1fc49..0d24c7c 100644 --- a/web/app.css +++ b/web/app.css @@ -83,7 +83,7 @@ button { } /* #app — колонка ровно в высоту окна: от неё считают высоту экраны, - поэтому сайдбар с «ты: @nick» стоит на месте, а прокручивается + поэтому профиль в подвале сайдбара стоит на месте, а прокручивается только содержимое */ #app { @@ -437,7 +437,8 @@ input[type="password"] { color: var(--mute); } -/* подвал сайдбара: «ты: @nick» и версия с временем коммита (ADR-067). +/* подвал сайдбара: пиктограмма профиля, @nick и версия с временем коммита + (ADR-067, ADR-079). Версия не кнопка: нажимать в ней нечего, поэтому она стоит рядом с кнопкой, а не внутри неё. В сайдбаре 224 px они рядом не помещаются никогда — версия занимает вторую строку и остаётся справа; на мобильном, @@ -473,11 +474,11 @@ input[type="password"] { cursor: pointer; } -.me i { +.me__icon { + display: block; flex: none; - width: 6px; - height: 6px; - background: var(--ink); + width: 16px; + height: 16px; } /* ник бывает в 32 символа (ADR-019): он обрезается, а версия остаётся @@ -561,6 +562,17 @@ input[type="password"] { max-width: 460px; } +/* На экране настроек все поля и действия занимают одну ширину. Глобальный + предел .form остаётся у входа и остальных коротких форм. */ + +.settings--account .form { + max-width: none; +} + +.settings__column { + min-width: 0; +} + .block { padding: 20px 0; border-top: 1px solid var(--line); @@ -1110,9 +1122,38 @@ input[type="password"] { } } +/* На широком десктопе настройки используют место справа от сайдбара: + повседневные действия и действия аккаунта идут в двух равных колонках. */ + +@media (min-width: 960px) { + .settings--account { + /* 752 px содержимого: две колонки по 360 px и промежуток 32 px; + ещё 64 px занимает desktop-padding самой .body */ + max-width: 816px; + } + + .settings__grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + column-gap: 32px; + align-items: start; + } +} + /* мобильный: один экран за раз, цели нажатия не меньше 44 px */ @media (max-width: 759px) { + /* На мобильном версия и профиль остаются в одной строке; место под + фиксированную версию отдаёт ник, который умеет обрезаться. */ + + .foot { + flex-wrap: nowrap; + } + + .me { + flex-basis: 0; + } + .shell[data-screen="list"] .main { display: none; } diff --git a/web/icons/profile.svg b/web/icons/profile.svg new file mode 100644 index 0000000..076de2b --- /dev/null +++ b/web/icons/profile.svg @@ -0,0 +1 @@ + diff --git a/web/js/ui/settings.js b/web/js/ui/settings.js index 3cc1d08..8841631 100644 --- a/web/js/ui/settings.js +++ b/web/js/ui/settings.js @@ -50,13 +50,21 @@ const IMPORT_FAILED = "импорт не удался"; export function renderSettings(root, ctx) { root.append(head(ctx)); - const body = el("div", "body settings"); - body.append(identity(ctx), notificationsBlock(ctx)); + const body = el("div", "body settings settings--account"); + const general = el("div", "settings__column"); + general.append(notificationsBlock(ctx)); const install = installBlock(); if (install !== null) { - body.append(install); + general.append(install); } - body.append(devicesBlock(ctx), historyBlock(), passwordBlock(ctx), exitBlock(ctx), deleteBlock(ctx)); + general.append(devicesBlock(ctx), historyBlock()); + + const security = el("div", "settings__column"); + security.append(passwordBlock(ctx), exitBlock(ctx), deleteBlock(ctx)); + + const grid = el("div", "settings__grid"); + grid.append(general, security); + body.append(identity(ctx), grid); root.append(body); } diff --git a/web/js/ui/shell.js b/web/js/ui/shell.js index 3e26af8..7335616 100644 --- a/web/js/ui/shell.js +++ b/web/js/ui/shell.js @@ -47,9 +47,12 @@ function side(ctx, active) { const foot = el("div", "foot"); const me = el("button", "me"); me.type = "button"; - const dot = el("i"); - dot.setAttribute("aria-hidden", "true"); - me.append(dot, el("span", "me__nick", `ты: @${ctx.me.nick}`)); + me.setAttribute("aria-label", `настройки: @${ctx.me.nick}`); + const profile = el("img", "me__icon"); + profile.src = "/icons/profile.svg"; + profile.alt = ""; + profile.draggable = false; + me.append(profile, el("span", "me__nick", `@${ctx.me.nick}`)); me.addEventListener("click", () => ctx.go("#/settings")); foot.append(me); const version = build(ctx.config); diff --git a/web/sw.js b/web/sw.js index ee52f64..c4a77be 100644 --- a/web/sw.js +++ b/web/sw.js @@ -7,7 +7,7 @@ // Имя кэша содержит версию; версия — константа, она меняется при релизе, // и старые кэши уходят в activate. -const VERSION = "v8"; +const VERSION = "v9"; const CACHE = `bare-${VERSION}`; // Оболочка — всё, из чего клиент поднимается без сети. Список явный: @@ -37,6 +37,7 @@ const SHELL = [ "/js/ui/shell.js", "/icons/icon.svg", "/icons/mark.svg", + "/icons/profile.svg", "/icons/icon-180.png", "/icons/icon-192.png", "/icons/icon-512.png",