UI v4: новый стиль по макету V3/V4 и доводка экранов

Палитра «Сияние» по умолчанию, новые роли цветов. Лента канала: плоские записи, ряд действий
с равным шагом иконок, дата у записи. Обсуждение со строкой ввода, личный чат, шторки.
Вход и регистрация: стартовый экран (надпись «СИЯНИЕ», сияние мандалы), настройки входа
со статусами серверов, статус логина в одну строку, вход с другого устройства (код, таймер, шаги),
сохранение ключей с переключателями. Корневые разделы – заголовок по центру; фильтр чатов –
переключатель; уведомления – список как в чатах; фото – просмотр на весь экран.
Общее: один зазор под шапкой (--page-top-gap), переключатели высотой с поле ввода,
второстепенные кнопки по одной роли (в том числе на экранах устройств и серверов),
иконки только из ui-icon.js, полоса прокрутки скрыта, среднее тире в текстах интерфейса.

Что нужно от сервера и разработчика – docs/UI-Design/UI-v4-for-dev.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
q
2026-09-29 01:36:52 +03:00
co-authored by Claude Opus 5.5
parent 13693a0a53
commit 2e62616b46
84 changed files with 2507 additions and 1583 deletions
+57 -57
View File
@@ -1078,8 +1078,8 @@ export function render({navigate, chrome}) {
const currentCoef = formatPpmCoefText(currentCore.coef.coefPpm);
const usdRaw = String(amountInput.value || '').trim().replace(',', '.');
let amountUsdCents = 0n;
let amountSol = '—';
let maxPaySol = '—';
let amountSol = '–';
let maxPaySol = '–';
let canBuy = true;
try {
const asNumber = Number(usdRaw);
@@ -1324,26 +1324,26 @@ export function render({navigate, chrome}) {
limitLabel.textContent = 'Лимит блокчейна';
const limitValue = document.createElement('h2');
limitValue.style.fontSize = '26px';
limitValue.textContent = '— KB';
limitValue.textContent = '– KB';
const usedLabel = document.createElement('p');
usedLabel.className = 'meta-muted';
usedLabel.textContent = 'Израсходовано (фактически на сервере)';
const usedValue = document.createElement('h2');
usedValue.style.fontSize = '26px';
usedValue.textContent = '— KB';
usedValue.textContent = '– KB';
const leftLabel = document.createElement('p');
leftLabel.className = 'meta-muted';
leftLabel.textContent = 'Осталось';
const leftValue = document.createElement('h2');
leftValue.style.fontSize = '30px';
leftValue.textContent = '— KB';
leftValue.textContent = '– KB';
const pdaLabel = document.createElement('p');
pdaLabel.className = 'meta-muted';
pdaLabel.style.wordBreak = 'break-all';
pdaLabel.textContent = 'PDA: —';
pdaLabel.textContent = 'PDA: –';
const endpointLabel = document.createElement('p');
endpointLabel.className = 'meta-muted';
@@ -1351,22 +1351,22 @@ export function render({navigate, chrome}) {
const updatedLabel = document.createElement('p');
updatedLabel.className = 'meta-muted';
updatedLabel.textContent = 'Обновлено: —';
updatedLabel.textContent = 'Обновлено: –';
const serverTitle = document.createElement('h3');
serverTitle.style.margin = '14px 0 0';
serverTitle.textContent = 'Фактическое состояние на сервере';
const serverSizeLabel = document.createElement('p');
serverSizeLabel.className = 'meta-muted';
serverSizeLabel.textContent = 'Размер цепочки: —';
serverSizeLabel.textContent = 'Размер цепочки: –';
const serverLastLabel = document.createElement('p');
serverLastLabel.className = 'meta-muted';
serverLastLabel.textContent = 'Крайний блок: —';
serverLastLabel.textContent = 'Крайний блок: –';
const serverLastHashLabel = document.createElement('p');
serverLastHashLabel.className = 'meta-muted';
serverLastHashLabel.style.wordBreak = 'break-all';
serverLastHashLabel.style.fontSize = '11px';
serverLastHashLabel.textContent = 'Hash: —';
serverLastHashLabel.textContent = 'Hash: –';
card.append(
limitLabel,
@@ -1420,7 +1420,7 @@ export function render({navigate, chrome}) {
serverSizeLabel.textContent = `Размер цепочки: ${formatKbFromBytes(serverState.sizeBytes)}`;
serverLastLabel.textContent = `Крайний блок: ${serverState.lastNumber}`;
serverLastHashLabel.textContent = `Hash: ${serverState.lastHash || '—'}`;
serverLastHashLabel.textContent = `Hash: ${serverState.lastHash || '–'}`;
setStatus('Данные лимита и состояния блокчейна обновлены.');
} catch (error) {
@@ -1503,8 +1503,8 @@ export function render({navigate, chrome}) {
publicationStatus.className = 'card stack';
publicationStatus.innerHTML = `
<h3 style="margin:0;">Статус публикации</h3>
<p class="meta-muted" id="publish-status-main">—</p>
<p class="meta-muted" id="publish-status-local">—</p>
<p class="meta-muted" id="publish-status-main">–</p>
<p class="meta-muted" id="publish-status-local">–</p>
`;
const publicationMainEl = publicationStatus.querySelector('#publish-status-main');
const publicationLocalEl = publicationStatus.querySelector('#publish-status-local');
@@ -1513,13 +1513,13 @@ export function render({navigate, chrome}) {
pdaCard.className = 'card stack';
pdaCard.innerHTML = `
<h3 style="margin:0;">Что закреплено в PDA</h3>
<p class="meta-muted" id="pda-name">Блокчейн: —</p>
<p class="meta-muted" id="pda-address" style="word-break:break-all;">PDA: —</p>
<p class="meta-muted" id="pda-last">Крайний блок: —</p>
<p class="meta-muted" id="pda-hash" style="word-break:break-all; font-size:11px;">Hash: —</p>
<p class="meta-muted" id="pda-used">Размер: —</p>
<p class="meta-muted" id="pda-limit">Лимит: —</p>
<p class="meta-muted" id="pda-arweave" style="word-break:break-all;">Arweave tx: —</p>
<p class="meta-muted" id="pda-name">Блокчейн: –</p>
<p class="meta-muted" id="pda-address" style="word-break:break-all;">PDA: –</p>
<p class="meta-muted" id="pda-last">Крайний блок: –</p>
<p class="meta-muted" id="pda-hash" style="word-break:break-all; font-size:11px;">Hash: –</p>
<p class="meta-muted" id="pda-used">Размер: –</p>
<p class="meta-muted" id="pda-limit">Лимит: –</p>
<p class="meta-muted" id="pda-arweave" style="word-break:break-all;">Arweave tx: –</p>
<a class="text-btn" id="pda-arweave-link" href="#" target="_blank" rel="noreferrer noopener" style="pointer-events:none; opacity:.55; padding:0;">Открыть Arweave</a>
`;
@@ -1527,25 +1527,25 @@ export function render({navigate, chrome}) {
serverCard.className = 'card stack';
serverCard.innerHTML = `
<h3 style="margin:0;">Что реально на сервере</h3>
<p class="meta-muted" id="server-name">Блокчейн: —</p>
<p class="meta-muted" id="server-last">Крайний блок: —</p>
<p class="meta-muted" id="server-hash" style="word-break:break-all; font-size:11px;">Hash: —</p>
<p class="meta-muted" id="server-size">Размер: —</p>
<p class="meta-muted" id="server-updated">Обновлено: —</p>
<p class="meta-muted" id="server-name">Блокчейн: –</p>
<p class="meta-muted" id="server-last">Крайний блок: –</p>
<p class="meta-muted" id="server-hash" style="word-break:break-all; font-size:11px;">Hash: –</p>
<p class="meta-muted" id="server-size">Размер: –</p>
<p class="meta-muted" id="server-updated">Обновлено: –</p>
`;
const localCard = document.createElement('div');
localCard.className = 'card stack';
localCard.innerHTML = `
<h3 style="margin:0;">Локальный слепок в этом браузере</h3>
<p class="meta-muted" id="local-state">Слепок: —</p>
<p class="meta-muted" id="local-transport">Способ: —</p>
<p class="meta-muted" id="local-last">Крайний блок: —</p>
<p class="meta-muted" id="local-hash" style="word-break:break-all; font-size:11px;">Hash: —</p>
<p class="meta-muted" id="local-size">Размер: —</p>
<p class="meta-muted" id="local-arweave" style="word-break:break-all;">Arweave tx: —</p>
<p class="meta-muted" id="local-created">Создан: —</p>
<p class="meta-muted" id="local-solana">Solana: —</p>
<p class="meta-muted" id="local-state">Слепок: –</p>
<p class="meta-muted" id="local-transport">Способ: –</p>
<p class="meta-muted" id="local-last">Крайний блок: –</p>
<p class="meta-muted" id="local-hash" style="word-break:break-all; font-size:11px;">Hash: –</p>
<p class="meta-muted" id="local-size">Размер: –</p>
<p class="meta-muted" id="local-arweave" style="word-break:break-all;">Arweave tx: –</p>
<p class="meta-muted" id="local-created">Создан: –</p>
<p class="meta-muted" id="local-solana">Solana: –</p>
`;
const actions = document.createElement('div');
@@ -1631,13 +1631,13 @@ export function render({navigate, chrome}) {
}
publicationLocalEl.textContent = localStatusText;
pdaNameEl.textContent = `Блокчейн: ${usage.blockchainName || '—'}`;
pdaAddressEl.textContent = `PDA: ${usage.userPda || '—'}`;
pdaNameEl.textContent = `Блокчейн: ${usage.blockchainName || '–'}`;
pdaAddressEl.textContent = `PDA: ${usage.userPda || '–'}`;
pdaLastEl.textContent = `Крайний блок: ${usage.lastBlockNumber}`;
pdaHashEl.textContent = `Hash: ${usage.lastBlockHashHex || '—'}`;
pdaHashEl.textContent = `Hash: ${usage.lastBlockHashHex || '–'}`;
pdaUsedEl.textContent = `Размер: ${formatBytesRu(usage.usedBytes)} байт`;
pdaLimitEl.textContent = `Лимит: ${formatBytesRu(usage.paidLimitBytes)} байт`;
pdaArweaveEl.textContent = `Arweave tx: ${usage.arweaveTxId || '—'}`;
pdaArweaveEl.textContent = `Arweave tx: ${usage.arweaveTxId || '–'}`;
if (usage.arweaveTxId) {
pdaArweaveLinkEl.href = buildArweaveDataUrl({ gateway: state.entrySettings.arweaveServer, txId: usage.arweaveTxId });
pdaArweaveLinkEl.style.pointerEvents = 'auto';
@@ -1648,32 +1648,32 @@ export function render({navigate, chrome}) {
pdaArweaveLinkEl.style.opacity = '.55';
}
serverNameEl.textContent = `Блокчейн: ${serverState.blockchainName || usage.blockchainName || '—'}`;
serverNameEl.textContent = `Блокчейн: ${serverState.blockchainName || usage.blockchainName || '–'}`;
serverLastEl.textContent = `Крайний блок: ${serverState.lastNumber}`;
serverHashEl.textContent = `Hash: ${serverState.lastHash || '—'}`;
serverHashEl.textContent = `Hash: ${serverState.lastHash || '–'}`;
serverSizeEl.textContent = `Размер: ${formatBytesRu(serverState.sizeBytes)} байт`;
serverUpdatedEl.textContent = `Обновлено: ${nowRu()}`;
if (localSnapshot?.txId) {
localStateEl.textContent = `Слепок: ${String(localSnapshot.txId || '') === String(usage.arweaveTxId || '') ? 'закреплён' : 'есть, но не закреплён в Solana'}`;
localTransportEl.textContent = `Способ: ${localSnapshot.uploadTransport === 'turbo' ? 'Turbo' : (localSnapshot.uploadTransport === 'arweave' ? 'Arweave' : '—')}`;
localLastEl.textContent = `Крайний блок: ${localSnapshot.lastBlockNumber ?? '—'}`;
localHashEl.textContent = `Hash: ${localSnapshot.lastBlockHash || '—'}`;
localTransportEl.textContent = `Способ: ${localSnapshot.uploadTransport === 'turbo' ? 'Turbo' : (localSnapshot.uploadTransport === 'arweave' ? 'Arweave' : '–')}`;
localLastEl.textContent = `Крайний блок: ${localSnapshot.lastBlockNumber ?? '–'}`;
localHashEl.textContent = `Hash: ${localSnapshot.lastBlockHash || '–'}`;
localSizeEl.textContent = `Размер: ${formatBytesRu(localSnapshot.usedBytes || 0)} байт`;
localArweaveEl.textContent = `Arweave tx: ${localSnapshot.txId}`;
localCreatedEl.textContent = `Создан: ${localSnapshot.uploadedAtMs ? new Date(localSnapshot.uploadedAtMs).toLocaleString('ru-RU') : '—'}`;
localCreatedEl.textContent = `Создан: ${localSnapshot.uploadedAtMs ? new Date(localSnapshot.uploadedAtMs).toLocaleString('ru-RU') : '–'}`;
localSolanaEl.textContent = localSnapshot.solanaUpdatedAtMs
? `Solana обновлена: ${new Date(localSnapshot.solanaUpdatedAtMs).toLocaleString('ru-RU')}`
: 'Solana: слепок ещё не закреплён';
} else {
localStateEl.textContent = 'Слепок: отсутствует';
localTransportEl.textContent = 'Способ: —';
localLastEl.textContent = 'Крайний блок: —';
localHashEl.textContent = 'Hash: —';
localSizeEl.textContent = 'Размер: —';
localArweaveEl.textContent = 'Arweave tx: —';
localCreatedEl.textContent = 'Создан: —';
localSolanaEl.textContent = 'Solana: —';
localTransportEl.textContent = 'Способ: –';
localLastEl.textContent = 'Крайний блок: –';
localHashEl.textContent = 'Hash: –';
localSizeEl.textContent = 'Размер: –';
localArweaveEl.textContent = 'Arweave tx: –';
localCreatedEl.textContent = 'Создан: –';
localSolanaEl.textContent = 'Solana: –';
}
publishBtn.disabled = !localSnapshot?.txId || !localMatchesServer || String(localSnapshot.txId || '') === String(usage.arweaveTxId || '');
@@ -1854,10 +1854,10 @@ export function render({navigate, chrome}) {
balanceLabel.textContent = 'Баланс (Solana)';
const balanceValue = document.createElement('h2');
balanceValue.style.fontSize = '30px';
balanceValue.textContent = '— SOL';
balanceValue.textContent = '– SOL';
const updatedLabel = document.createElement('p');
updatedLabel.className = 'meta-muted';
updatedLabel.textContent = 'Обновлено: —';
updatedLabel.textContent = 'Обновлено: –';
const endpointLabel = document.createElement('p');
endpointLabel.className = 'meta-muted';
endpointLabel.textContent = `RPC: ${state.entrySettings.solanaServer}`;
@@ -1868,7 +1868,7 @@ export function render({navigate, chrome}) {
addressCard.style.padding = '10px';
addressCard.innerHTML = `
<p class="meta-muted" style="margin-bottom:6px;">Публичный адрес (client.key)</p>
<p style="font-size:13px; line-height:1.4; word-break:break-all;" id="wallet-address-value">—</p>
<p style="font-size:13px; line-height:1.4; word-break:break-all;" id="wallet-address-value">–</p>
`;
const addressEl = addressCard.querySelector('#wallet-address-value');
@@ -2000,10 +2000,10 @@ export function render({navigate, chrome}) {
balanceLabel.textContent = 'Баланс (Arweave)';
const balanceValue = document.createElement('h2');
balanceValue.style.fontSize = '30px';
balanceValue.textContent = '— AR';
balanceValue.textContent = '– AR';
const updatedLabel = document.createElement('p');
updatedLabel.className = 'meta-muted';
updatedLabel.textContent = 'Обновлено: —';
updatedLabel.textContent = 'Обновлено: –';
const gatewayLabel = document.createElement('p');
gatewayLabel.className = 'meta-muted';
gatewayLabel.textContent = `Gateway: ${state.entrySettings.arweaveServer}`;
@@ -2014,7 +2014,7 @@ export function render({navigate, chrome}) {
addressCard.style.padding = '10px';
addressCard.innerHTML = `
<p class="meta-muted" style="margin-bottom:6px;">Публичный адрес Arweave (SAWD-v1)</p>
<p style="font-size:13px; line-height:1.4; word-break:break-all;" id="wallet-address-value">—</p>
<p style="font-size:13px; line-height:1.4; word-break:break-all;" id="wallet-address-value">–</p>
`;
const addressEl = addressCard.querySelector('#wallet-address-value');
@@ -2133,7 +2133,7 @@ export function render({navigate, chrome}) {
if (!text) return;
if (text.includes('впервые получаем Arweave-кошелёк')) {
wasFirstTimeGeneration = true;
setStatus('Подождите — ваш Arweave-ключ вычисляется из client key. Это происходит только один раз, потом будет мгновенно.');
setStatus('Подождите – ваш Arweave-ключ вычисляется из client key. Это происходит только один раз, потом будет мгновенно.');
return;
}
setStatus(text);