Редизайн под новую Matrix-стилистику + RU/EN переключатель языка

Новая шапка (sticky, blur, мигающий курсор), нумерованные секции резюме
с полным опытом работы и новым разделом «Образование», карточки
проектов в стиле drwxr-xr-x, более яркий дождь на фоне. Добавлен
i18n-модуль (frontend/js/i18n.js) с переключателем EN/RU: резюме и
проекты полностью переведены, в игре переведены статичные подписи
интерфейса лобби и боя (данные от сервера остаются на русском).
This commit is contained in:
2026-08-09 21:42:57 +05:00
parent 201506bdbc
commit a1b123dd13
13 changed files with 658 additions and 186 deletions
+56 -14
View File
@@ -2,9 +2,49 @@ import { Network, ServerMessage } from './network.js';
import { ClientMessage } from './protocol.js';
import { startBattle } from './battle.js';
import { unlockAudio } from './audio.js';
import { getLang } from '../i18n.js';
const container = document.getElementById('lobby');
// Статичные подписи UI лобби переводим; данные из сервера (ники, room id,
// текст ошибок) остаются как есть — они генерируются на сервере только на русском.
const STR = {
ru: {
connecting: 'подключение...',
join: 'войти',
createCoop: 'создать комнату (coop)',
createPvp: 'создать комнату (pvp)',
refresh: 'обновить список',
empty: 'пока пусто — создай комнату',
ready: 'готов',
readySuffix: ' — готов',
waitingForSecond: 'жду второго игрока (1/2) — открой комнату во второй вкладке/другим человеком',
soloHint: 'можно начать соло, второй игрок сможет подключиться позже',
room: 'комната',
mode: 'режим:',
leave: 'выйти в лобби',
},
en: {
connecting: 'connecting...',
join: 'join',
createCoop: 'create room (coop)',
createPvp: 'create room (pvp)',
refresh: 'refresh list',
empty: 'nothing here yet — create a room',
ready: 'ready',
readySuffix: ' — ready',
waitingForSecond: 'waiting for a second player (1/2) — open the room in another tab or have someone else join',
soloHint: 'you can start solo, a second player can join later',
room: 'room',
mode: 'mode:',
leave: 'leave to lobby',
},
};
function t() {
return STR[getLang()];
}
let net;
let myPlayerId = null;
let rooms = [];
@@ -33,7 +73,7 @@ function roomRow(room) {
<span class="tag">${room.mode}</span>
<span class="room-list__count">${room.players}/${room.max_players}</span>
<span class="room-list__state">${room.state}</span>
<button data-action="join" data-room-id="${room.id}" ${full || room.state !== 'waiting' || !connected ? 'disabled' : ''}>войти</button>
<button data-action="join" data-room-id="${room.id}" ${full || room.state !== 'waiting' || !connected ? 'disabled' : ''}>${t().join}</button>
</li>`;
}
@@ -46,18 +86,18 @@ function renderLobbyView() {
container.innerHTML = `
<div class="lobby-panel">
${errorBanner()}
${connected ? '' : '<p class="lobby-error">подключение...</p>'}
${connected ? '' : `<p class="lobby-error">${t().connecting}</p>`}
<div class="lobby-panel__row">
<label class="prompt-inline">nickname</label>
<input id="nickname-input" type="text" value="${escapeHtml(nickname)}" maxlength="20" ${dis} />
</div>
<div class="lobby-panel__row">
<button data-action="create" data-mode="coop" ${dis}>создать комнату (coop)</button>
<button data-action="create" data-mode="pvp" ${dis}>создать комнату (pvp)</button>
<button data-action="refresh" ${dis}>обновить список</button>
<button data-action="create" data-mode="coop" ${dis}>${t().createCoop}</button>
<button data-action="create" data-mode="pvp" ${dis}>${t().createPvp}</button>
<button data-action="refresh" ${dis}>${t().refresh}</button>
</div>
<ul class="room-list">
${rooms.length === 0 ? '<li class="room-list__empty">пока пусто — создай комнату</li>' : rooms.map(roomRow).join('')}
${rooms.length === 0 ? `<li class="room-list__empty">${t().empty}</li>` : rooms.map(roomRow).join('')}
</ul>
</div>`;
}
@@ -65,24 +105,24 @@ function renderLobbyView() {
function renderRoomView() {
const full = currentRoom.players.length >= 2;
let statusLine = '';
let readyBtn = '<button data-action="ready">готов</button>';
let readyBtn = `<button data-action="ready">${t().ready}</button>`;
if (currentRoom.mode === 'pvp' && !full) {
statusLine = '<p class="lobby-error">жду второго игрока (1/2) — открой комнату во второй вкладке/другим человеком</p>';
readyBtn = '<button data-action="ready" disabled>готов</button>';
statusLine = `<p class="lobby-error">${t().waitingForSecond}</p>`;
readyBtn = `<button data-action="ready" disabled>${t().ready}</button>`;
} else if (currentRoom.mode === 'coop' && !full) {
statusLine = '<p class="lobby-error">можно начать соло, второй игрок сможет подключиться позже</p>';
statusLine = `<p class="lobby-error">${t().soloHint}</p>`;
}
container.innerHTML = `
<div class="lobby-panel">
${errorBanner()}
<h3 class="prompt">room ${currentRoom.name}</h3>
<p>режим: <span class="tag">${currentRoom.mode}</span></p>
<h3 class="prompt">${t().room} ${currentRoom.name}</h3>
<p>${t().mode} <span class="tag">${currentRoom.mode}</span></p>
<ul class="room-players">
${currentRoom.players.map((p) => `<li>${escapeHtml(p.nickname)}${p.ready ? ' — готов' : ''}</li>`).join('')}
${currentRoom.players.map((p) => `<li>${escapeHtml(p.nickname)}${p.ready ? t().readySuffix : ''}</li>`).join('')}
</ul>
${statusLine}
${readyBtn}
<button data-action="leave">выйти в лобби</button>
<button data-action="leave">${t().leave}</button>
</div>`;
}
@@ -180,6 +220,8 @@ export function initLobby() {
render();
});
window.addEventListener('cacto:langchange', render);
render();
return net;
}