Редизайн под новую 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
+54 -15
View File
@@ -1,6 +1,38 @@
import { ClientMessage, ServerMessage } from './protocol.js';
import { returnToLobbyContainer, leaveRoomAndReturn } from './lobby.js';
import { playShot, playWallHit, playExplosion, playWin, playLose } from './audio.js';
import { getLang } from '../i18n.js';
const STR = {
ru: {
controlsHint: 'WASD / стрелки + пробел тоже работают',
fire: 'огонь',
titles: { win: 'ПОБЕДА', lose: 'ПОРАЖЕНИЕ', draw: 'НИЧЬЯ' },
rematch: 'играть ещё',
toLobby: 'в лобби',
},
en: {
controlsHint: 'WASD / arrow keys + space also work',
fire: 'fire',
titles: { win: 'VICTORY', lose: 'DEFEAT', draw: 'DRAW' },
rematch: 'play again',
toLobby: 'to lobby',
},
};
function t() {
return STR[getLang()];
}
// Регистрируем один раз на модуль: обновляем статичные подписи в уже
// отрисованной панели управления и оверлей победы/поражения, если он открыт.
window.addEventListener('cacto:langchange', () => {
const hint = document.querySelector('.battle-controls__hint');
if (hint) hint.textContent = t().controlsHint;
const fireBtn = document.querySelector('.fire-btn');
if (fireBtn) fireBtn.textContent = t().fire;
if (lastOverPayload) renderOverlay(lastOverPayload);
});
const TILE_PX = 32;
// Quintus транслирует и вращает контекст сам (Sprite.render -> matrix.setContextTransform)
@@ -282,6 +314,23 @@ function onState(payload) {
}
}
let lastOverPayload = null;
function renderOverlay(payload) {
const title = t().titles[payload.result] || payload.result.toUpperCase();
const overlay = document.getElementById('battle-result-overlay');
overlay.innerHTML = `
<div class="battle-result battle-result--${payload.result}">
<h2 class="glitch" data-text="${title}">${title}</h2>
<p>${payload.reason}</p>
<div class="battle-result__actions">
<button data-action="rematch">${t().rematch}</button>
<button data-action="to-lobby">${t().toLobby}</button>
</div>
</div>`;
overlay.style.display = 'flex';
}
function onOver(payload) {
polling = false;
if (payload.result === 'win') {
@@ -289,19 +338,8 @@ function onOver(payload) {
} else {
playLose();
}
const titles = { win: 'ПОБЕДА', lose: 'ПОРАЖЕНИЕ', draw: 'НИЧЬЯ' };
const title = titles[payload.result] || payload.result.toUpperCase();
const overlay = document.getElementById('battle-result-overlay');
overlay.innerHTML = `
<div class="battle-result battle-result--${payload.result}">
<h2 class="glitch" data-text="${title}">${title}</h2>
<p>${payload.reason}</p>
<div class="battle-result__actions">
<button data-action="rematch">играть ещё</button>
<button data-action="to-lobby">в лобби</button>
</div>
</div>`;
overlay.style.display = 'flex';
lastOverPayload = payload;
renderOverlay(payload);
}
function onOverlayClick(e) {
@@ -310,6 +348,7 @@ function onOverlayClick(e) {
const overlay = document.getElementById('battle-result-overlay');
overlay.style.display = 'none';
overlay.innerHTML = '';
lastOverPayload = null;
if (btn.dataset.action === 'rematch') {
returnToLobbyContainer();
} else {
@@ -331,14 +370,14 @@ export function startBattle(networkInstance, payload, ownPlayerId) {
<div class="battle-layout">
<div id="battle-canvas-slot"></div>
<div class="battle-controls">
<p class="battle-controls__hint">WASD / стрелки + пробел тоже работают</p>
<p class="battle-controls__hint">${t().controlsHint}</p>
<div class="dpad">
<button class="dpad__up" data-input="up">&uarr;</button>
<button class="dpad__left" data-input="left">&larr;</button>
<button class="dpad__right" data-input="right">&rarr;</button>
<button class="dpad__down" data-input="down">&darr;</button>
</div>
<button class="fire-btn" data-input="fire">огонь</button>
<button class="fire-btn" data-input="fire">${t().fire}</button>
</div>
</div>
<div id="battle-result-overlay"></div>`;