feat(ui): replace gameplay icons with local SVGs
This commit is contained in:
+7
-2
@@ -1,5 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="key" viewBox="0 0 24 24"><circle cx="8" cy="9" r="4" fill="none" stroke="currentColor" stroke-width="3"/><path d="M11 12l9 9m-4-4 2-2m-5-1 2-2" fill="none" stroke="currentColor" stroke-width="3"/></symbol>
|
||||
<!-- Reference-derived gameplay symbols use a shared 64 by 64 view box and currentColor. -->
|
||||
<symbol id="player" viewBox="0 0 64 64"><path fill="currentColor" d="M37 5a9 9 0 1 1 0 18 9 9 0 0 1 0-18Zm-8 20 10-2 10 7 8-5a6 6 0 1 1 6 10l-12 8a6 6 0 0 1-7 0l-7-5-3 11 12 8a6 6 0 0 1-7 10L24 57a6 6 0 0 1-2-7l4-15-7 4-4 10a6 6 0 1 1-11-4l5-14a6 6 0 0 1 3-3l13-7a6 6 0 0 1 4 4Z"/></symbol>
|
||||
<symbol id="key" viewBox="0 0 64 64"><path fill="currentColor" fill-rule="evenodd" d="M43 5a17 17 0 0 0-14 27L5 56l3 3 5-2 3 4 5-2 3 4 6-6-4-4 7-7A17 17 0 1 0 43 5Zm0 11a6 6 0 1 1 0 12 6 6 0 0 1 0-12Z" clip-rule="evenodd"/></symbol>
|
||||
<symbol id="exit-closed" viewBox="0 0 64 64"><path fill="currentColor" d="M15 59V29C15 10 49 10 49 29v30H15Zm10-30v30h14V29c0-8-14-8-14 0Zm-17-8 6 2-2 10-7-2 3-10Zm42 2 6-2 3 10-7 2-2-10ZM7 38h7v11H7V38Zm43 0h7v11h-7V38ZM14 9l7-5 4 9-7 3-4-7Zm32-5 7 5-4 7-7-3 4-9Z"/></symbol>
|
||||
<symbol id="exit-open" viewBox="0 0 64 64"><path fill="currentColor" d="M15 59V29C15 10 49 10 49 29v30H15Zm10-30v30h14V29c0-8-14-8-14 0ZM7 38h7v11H7V38Zm43 0h7v11h-7V38ZM14 9l7-5 4 9-7 3-4-7Zm32-5 7 5-4 7-7-3 4-9Z"/><path fill="currentColor" d="M25 36h10v-7l14 12-14 12v-7H25V36Z"/></symbol>
|
||||
<symbol id="crystal" viewBox="0 0 64 64"><path fill="currentColor" fill-rule="evenodd" d="m32 5 19 20-19 34L13 25 32 5Zm0 12-8 9h16l-8-9Zm-11 17 11 17 11-17H21Z" clip-rule="evenodd"/><path fill="currentColor" d="M32 1h4v8h-4V1ZM5 29h9v5H5v-5Zm45 0h9v5h-9v-5ZM10 11l6-6 4 4-6 6-4-4Zm34-2 4-4 6 6-4 4-6-6ZM10 53l4-4 6 6-4 4-6-6Zm34 2 6-6 4 4-6 6-4-4Z"/></symbol>
|
||||
<symbol id="spikes" viewBox="0 0 64 64"><path fill="currentColor" d="m9 47 9-32 9 32H9Zm18 0 9-32 9 32H27Zm18 0 9-32 9 32H45ZM5 51h54v9H5v-9Z"/></symbol>
|
||||
<symbol id="eye" viewBox="0 0 24 24"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12Z" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="12" cy="12" r="3" fill="currentColor"/></symbol>
|
||||
<symbol id="arrow" viewBox="0 0 24 24"><path d="M12 2l7 9h-4v11H9V11H5l7-9Z" fill="currentColor"/></symbol>
|
||||
<symbol id="signal" viewBox="0 0 24 24"><path d="M3 9a14 14 0 0 1 18 0M6 13a9 9 0 0 1 12 0m-9 4a4 4 0 0 1 6 0" fill="none" stroke="currentColor" stroke-width="2.5"/><circle cx="12" cy="21" r="1.5" fill="currentColor"/></symbol>
|
||||
@@ -9,7 +15,6 @@
|
||||
<symbol id="motion" viewBox="0 0 24 24"><path d="M4 12h6m4 0h6M8 7l5 5-5 5M16 7l-5 5 5 5" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></symbol>
|
||||
<symbol id="help" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2.5"/><path d="M9.5 9a2.7 2.7 0 1 1 4.2 2.2c-1.4.9-1.7 1.4-1.7 2.8M12 17h.01" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></symbol>
|
||||
<symbol id="hand" viewBox="0 0 24 24"><path d="M8 12V5a1.5 1.5 0 0 1 3 0v5V3.5a1.5 1.5 0 0 1 3 0V10V5a1.5 1.5 0 0 1 3 0v6V8a1.5 1.5 0 0 1 3 0v6c0 4-3 7-7 7-3 0-5-2-6-5l-1-3a1.5 1.5 0 0 1 2-1z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/></symbol>
|
||||
<symbol id="exit" viewBox="0 0 24 24"><path d="M4 3h12v18H4zM16 8h4v13H8" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linejoin="round"/><path d="m11 12 5-4m-5 4 5 4m-5-4H2" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></symbol>
|
||||
<symbol id="friends" viewBox="0 0 24 24"><circle cx="8" cy="8" r="3" fill="none" stroke="currentColor" stroke-width="2.5"/><circle cx="17" cy="10" r="3" fill="none" stroke="currentColor" stroke-width="2.5"/><path d="M3 21c0-4 2-7 5-7s5 3 5 7m1-1c.5-3 1.7-5 4-5 2 0 3 2 3 5" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></symbol>
|
||||
<symbol id="maze" viewBox="0 0 160 112"><path d="M8 8h144v96H8V8Zm20 0v24H8m44-24v20h28v28H52v24H28v24m52-48h28V32h24v48h20M80 80v24m28-48v48" fill="none" stroke="currentColor" stroke-width="8" stroke-linecap="square"/><circle cx="20" cy="92" r="7" fill="currentColor"/><path d="m135 89 9 7-9 7" fill="none" stroke="currentColor" stroke-width="6"/></symbol>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 4.4 KiB |
+6
-6
@@ -34,8 +34,8 @@
|
||||
<div class="lobby-grid">
|
||||
<article class="panel roster-panel">
|
||||
<h2>Игроки</h2>
|
||||
<div class="player-card player-one" id="player-1"><span class="avatar">P1</span><span><strong class="player-name">Свободно</strong><small class="player-state">Слот грустит</small></span><span class="status-dot"></span></div>
|
||||
<div class="player-card player-two" id="player-2"><span class="avatar">P2</span><span><strong class="player-name">Свободно</strong><small class="player-state">Слот грустит</small></span><span class="status-dot"></span></div>
|
||||
<div class="player-card player-one" id="player-1"><span class="avatar" aria-label="Жёлтый игрок"><svg aria-hidden="true"><use href="/icons.svg#player"></use></svg></span><span><strong class="player-name">Свободно</strong><small class="player-state">Слот грустит</small></span><span class="status-dot"></span></div>
|
||||
<div class="player-card player-two" id="player-2"><span class="avatar" aria-label="Синий игрок"><svg aria-hidden="true"><use href="/icons.svg#player"></use></svg></span><span><strong class="player-name">Свободно</strong><small class="player-state">Слот грустит</small></span><span class="status-dot"></span></div>
|
||||
</article>
|
||||
<article class="panel observer-panel"><svg aria-hidden="true"><use href="/icons.svg#eye"></use></svg><h2>Зрительный зал</h2><p><strong id="observer-count">0</strong> / 4 эксперта по чужим ошибкам.</p></article>
|
||||
<article class="panel launch-panel"><h2>Большая красная кнопка</h2><p id="launch-help">Старт доступен Игроку 1, когда оба слота на месте.</p><button class="button primary launch-button" id="start-match" type="button" disabled>Начать 3—2—1</button></article>
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<section class="screen match-screen" id="match-screen" hidden aria-label="Игровой экран">
|
||||
<div class="player-status" id="player-status">
|
||||
<div class="identity" id="identity"><span class="avatar character-avatar" id="self-slot" aria-hidden="true">●</span><span><strong id="self-name">Неизвестный герой</strong><small id="match-state">Лабиринт думает</small></span></div>
|
||||
<div class="identity" id="identity"><span class="avatar character-avatar" id="self-slot" aria-hidden="true"><svg><use href="/icons.svg#player"></use></svg></span><span><strong id="self-name">Неизвестный герой</strong><small id="match-state">Лабиринт думает</small></span></div>
|
||||
<div class="stat" id="inventory-stat"><svg aria-hidden="true"><use href="/icons.svg#key"></use></svg><span><small>Ключ</small><strong id="inventory">Пусто</strong></span></div>
|
||||
<div class="stat" id="vision-stat"><svg aria-hidden="true"><use href="/icons.svg#eye"></use></svg><span><small>Суперзрение</small><strong id="vision">Нет</strong></span></div>
|
||||
<div class="stat compass-stat" id="compass-stat"><svg id="compass-arrow" aria-hidden="true"><use href="/icons.svg#arrow"></use></svg><span><small>Компас</small><strong id="compass">Молчит</strong></span></div>
|
||||
@@ -52,8 +52,8 @@
|
||||
<div class="map-shell" id="map-shell" tabindex="0" aria-label="Лабиринт. Используйте стрелки, WASD или свайпы для движения">
|
||||
<div class="unknown-message" id="map-placeholder"><svg aria-hidden="true"><use href="/icons.svg#radar"></use></svg><span>Ждём разрешённый сервером кусочек карты…</span></div>
|
||||
<div class="maze" id="maze" role="img" aria-label="Видимая часть лабиринта"></div>
|
||||
<button class="avatar-control" id="avatar-control" type="button" aria-label="Тянуть героя для движения"><span class="avatar-handle" id="avatar-handle" aria-hidden="true"><span id="avatar-handle-symbol">●</span><svg><use href="/icons.svg#arrow"></use></svg></span></button>
|
||||
<div class="wall-bump" id="wall-bump" role="status" aria-live="polite"></div>
|
||||
<button class="avatar-control" id="avatar-control" type="button" aria-label="Тянуть героя для движения"><span class="avatar-handle" id="avatar-handle" aria-hidden="true"><svg class="avatar-player-icon"><use href="/icons.svg#player"></use></svg><svg class="avatar-direction-icon"><use href="/icons.svg#arrow"></use></svg></span></button>
|
||||
<div class="wall-bump" id="wall-bump" role="status" aria-live="polite"><svg aria-hidden="true"><use href="/icons.svg#spikes"></use></svg><span class="wall-bump-text"></span></div>
|
||||
</div>
|
||||
<div class="child-controls" aria-label="Игровые действия">
|
||||
<button class="drop-key" id="drop-key" type="button" aria-label="Сбросить ключ" disabled><svg aria-hidden="true"><use href="/icons.svg#key"></use></svg><strong>OK</strong><span>бросить</span></button>
|
||||
@@ -72,7 +72,7 @@
|
||||
<section class="screen observer-screen" id="observer-screen" hidden aria-labelledby="observer-title">
|
||||
<div class="observer-toolbar">
|
||||
<div><p class="eyebrow">Режим наблюдателя · только чтение</p><h1 id="observer-title">Весь куст целиком</h1></div>
|
||||
<div class="observer-players" aria-live="polite"><span class="player-one">P1 <strong id="observer-p1">готов</strong></span><span class="player-two">P2 <strong id="observer-p2">готов</strong></span><span id="observer-phase">Ждём старта</span></div>
|
||||
<div class="observer-players" aria-live="polite"><span class="player-one"><svg aria-hidden="true"><use href="/icons.svg#player"></use></svg> Жёлтый <strong id="observer-p1">готов</strong></span><span class="player-two"><svg aria-hidden="true"><use href="/icons.svg#player"></use></svg> Синий <strong id="observer-p2">готов</strong></span><span id="observer-phase">Ждём старта</span></div>
|
||||
<div class="zoom-controls" aria-label="Масштаб карты"><button id="zoom-out" type="button" aria-label="Уменьшить масштаб">−</button><button id="zoom-reset" type="button">Вся карта</button><button id="zoom-in" type="button" aria-label="Увеличить масштаб">+</button></div>
|
||||
</div>
|
||||
<div class="observer-requirement" id="observer-requirement" role="status"><svg aria-hidden="true"><use href="/icons.svg#eye"></use></svg><strong>Нужен экран побольше</strong><span>Зрительный зал открывается от 1024 × 768 пикселей.</span></div>
|
||||
|
||||
+32
-26
@@ -8,14 +8,15 @@
|
||||
--ink: #f6efff;
|
||||
--muted: #b7a8ca;
|
||||
--aqua: #43ead2;
|
||||
--p1: #58aaff;
|
||||
--p2: #ff72bd;
|
||||
--p1: #ffd33d;
|
||||
--p2: #41b6ff;
|
||||
--plant: #45b86b;
|
||||
--plant-dark: #176638;
|
||||
--success: #72e48c;
|
||||
--warning: #ffd65a;
|
||||
--warning: #ffad38;
|
||||
--vision: #37c9ff;
|
||||
--error: #ff647a;
|
||||
--pause: #5598ff;
|
||||
--pause: #9b8cff;
|
||||
--disconnect: #ff9f43;
|
||||
--cell: 40px;
|
||||
--shadow: #040207;
|
||||
@@ -84,8 +85,10 @@ input:focus { border-color: var(--aqua); box-shadow: 0 0 0 3px #43ead229; }
|
||||
.player-card.player-one { border-left: 5px solid var(--p1); }
|
||||
.player-card.player-two { border-left: 5px solid var(--p2); }
|
||||
.avatar { width: 46px; height: 46px; display: grid; place-items: center; border: 2px solid currentColor; background: #0b0712; font-weight: 1000; }
|
||||
.avatar svg { width: 82%; height: 82%; }
|
||||
.player-one .avatar, .identity.player-one .avatar { color: var(--p1); }
|
||||
.player-two .avatar, .identity.player-two .avatar { color: var(--p2); }
|
||||
.player-two .avatar { border-style: dashed; }
|
||||
.player-card strong, .player-card small, .identity strong, .identity small, .stat small, .stat strong { display: block; }
|
||||
.player-card small, .identity small, .stat small { margin-top: 4px; color: var(--muted); font-size: .66rem; }
|
||||
.status-dot { width: 10px; height: 10px; background: var(--error); }
|
||||
@@ -116,38 +119,41 @@ input:focus { border-color: var(--aqua); box-shadow: 0 0 0 3px #43ead229; }
|
||||
.cell.wall-w { border-left-color: var(--plant); }
|
||||
.cell::after { content: ""; position: absolute; inset: 7px; background: #1c1b27; opacity: .55; }
|
||||
.object, .pawn { position: absolute; z-index: 3; inset: 16%; display: grid; place-items: center; }
|
||||
.object svg { width: 100%; height: 100%; }
|
||||
.object-key { color: var(--warning); }
|
||||
.object-key::after { content: "●"; position: absolute; right: -5px; bottom: -6px; padding: 1px 3px; background: #090711; border: 1px solid currentColor; font-size: 8px; font-weight: 900; }
|
||||
.object-key[data-owner="two"]::after { content: "◆"; }
|
||||
.object-exit { inset: 8%; border: 3px double currentColor; background: #0b0911; }
|
||||
.object svg, .pawn svg { width: 100%; height: 100%; }
|
||||
.object-key { color: var(--p1); }
|
||||
.object-key[data-owner="two"] { color: var(--p2); }
|
||||
.object-key::after { content: ""; position: absolute; right: -4px; bottom: -4px; width: 8px; height: 8px; border: 2px solid currentColor; border-radius: 50%; background: #090711; }
|
||||
.object-key[data-owner="two"]::after { border-radius: 0; transform: rotate(45deg); }
|
||||
.object-exit { inset: 8%; background: #0b0911; }
|
||||
.object-exit[data-owner="one"] { color: var(--p1); }
|
||||
.object-exit[data-owner="two"] { color: var(--p2); transform: rotate(45deg); }
|
||||
.object-exit[data-owner="two"] { color: var(--p2); }
|
||||
.object-exit[data-owner="two"]::after { content: ""; position: absolute; inset: 6%; border: 2px dashed currentColor; }
|
||||
.object-exit[data-open="1"] { background: #72e48c35; animation: exit-open .8s steps(2, end) infinite; }
|
||||
.object-compass { color: var(--aqua); border: 2px dashed currentColor; border-radius: 50%; }
|
||||
.object-landmark { color: var(--plant); font-size: 1.15rem; text-shadow: 2px 2px #000; }
|
||||
.object-vision { color: var(--warning); border-radius: 50%; background: #ffd65a30; box-shadow: 0 0 10px var(--warning); animation: artifact 1.2s steps(4, end) infinite; }
|
||||
.pawn { inset: 12%; border: 3px solid currentColor; background: #0b0712; font-size: .62rem; font-weight: 1000; box-shadow: 3px 3px 0 #000; animation: pawn-in .16s steps(2, end); }
|
||||
.object-vision { color: var(--vision); border-radius: 50%; background: #37c9ff30; box-shadow: 0 0 10px var(--vision); animation: artifact 1.2s steps(4, end) infinite; }
|
||||
.pawn { inset: 12%; padding: 5%; border: 3px solid currentColor; background: #0b0712; box-shadow: 3px 3px 0 #000; animation: pawn-in .16s steps(2, end); }
|
||||
.pawn-self { color: var(--p1); }
|
||||
.match-screen.player-two-mode .pawn-self { color: var(--p2); }
|
||||
.pawn-opponent { color: var(--p2); transform: rotate(45deg); }
|
||||
.pawn-opponent { color: var(--p2); }
|
||||
.match-screen.player-two-mode .pawn-opponent { color: var(--p1); }
|
||||
.pawn-opponent span { transform: rotate(-45deg); }
|
||||
.wall-bump { position: absolute; left: 50%; bottom: 18px; z-index: 6; min-height: 28px; transform: translateX(-50%); padding: 6px 10px; color: var(--error); background: #260b12dd; opacity: 0; }
|
||||
.pawn-owner-two { border-style: dashed; }
|
||||
.wall-bump { position: absolute; left: 50%; bottom: 18px; z-index: 6; display: flex; align-items: center; gap: 6px; min-height: 28px; transform: translateX(-50%); padding: 6px 10px; color: var(--error); background: #260b12dd; opacity: 0; }
|
||||
.wall-bump.show { animation: bump .8s steps(3, end); }
|
||||
.wall-bump::before { content: "▌"; display: inline-block; margin-right: 6px; font-size: 1.35em; font-weight: 1000; }
|
||||
.wall-bump svg { width: 27px; height: 27px; flex: 0 0 27px; }
|
||||
|
||||
.avatar-control { position: absolute; z-index: 8; left: 50%; top: 50%; width: 96px; height: 96px; margin: -48px; padding: 0; border: 0; border-radius: 50%; background: transparent; cursor: grab; touch-action: none; user-select: none; }
|
||||
.avatar-control:active, .avatar-control.active { cursor: grabbing; }
|
||||
.avatar-handle { position: absolute; left: 50%; top: 50%; width: 52px; height: 52px; display: grid; place-items: center; border: 3px solid currentColor; border-radius: 50%; background: #0b0712; color: var(--p1); box-shadow: 4px 4px 0 #000, 0 0 0 3px #43ead236; font-size: 1.25rem; font-weight: 1000; transform: translate(-50%, -50%); transition: transform .12s steps(3, end); }
|
||||
.player-two-mode .avatar-handle { color: var(--p2); }
|
||||
.avatar-control.active .avatar-handle { transition: none; }
|
||||
.avatar-handle svg { position: absolute; width: 19px; height: 19px; right: -13px; top: -13px; display: none; padding: 2px; border: 1px solid currentColor; background: #08050b; color: var(--aqua); }
|
||||
.avatar-control[data-direction] .avatar-handle svg { display: block; }
|
||||
.avatar-control[data-direction="north"] .avatar-handle svg { transform: rotate(0deg); }
|
||||
.avatar-control[data-direction="east"] .avatar-handle svg { transform: rotate(90deg); }
|
||||
.avatar-control[data-direction="south"] .avatar-handle svg { transform: rotate(180deg); }
|
||||
.avatar-control[data-direction="west"] .avatar-handle svg { transform: rotate(270deg); }
|
||||
.avatar-player-icon { width: 74%; height: 74%; }
|
||||
.avatar-direction-icon { position: absolute; width: 19px; height: 19px; right: -13px; top: -13px; display: none; padding: 2px; border: 1px solid currentColor; background: #08050b; color: var(--aqua); }
|
||||
.avatar-control[data-direction] .avatar-direction-icon { display: block; }
|
||||
.avatar-control[data-direction="north"] .avatar-direction-icon { transform: rotate(0deg); }
|
||||
.avatar-control[data-direction="east"] .avatar-direction-icon { transform: rotate(90deg); }
|
||||
.avatar-control[data-direction="south"] .avatar-direction-icon { transform: rotate(180deg); }
|
||||
.avatar-control[data-direction="west"] .avatar-direction-icon { transform: rotate(270deg); }
|
||||
.avatar-control[data-speed="fast"] .avatar-handle { box-shadow: 4px 4px 0 #000, 0 0 0 4px var(--aqua), 0 0 18px #43ead299; }
|
||||
.avatar-control[data-speed="slow"] .avatar-handle { box-shadow: 4px 4px 0 #000, 0 0 0 3px #43ead270; }
|
||||
.child-controls { position: absolute; z-index: 12; inset: auto 0 12px; display: flex; align-items: end; justify-content: space-between; padding: 0 16px; pointer-events: none; }
|
||||
@@ -202,7 +208,8 @@ input:focus { border-color: var(--aqua); box-shadow: 0 0 0 3px #43ead229; }
|
||||
.observer-players { display: flex; align-items: center; gap: 12px; color: var(--muted); font-size: .72rem; }
|
||||
.observer-players span { padding: 7px; border: 1px solid #51386e; }
|
||||
.observer-players .player-one { border-color: var(--p1); }
|
||||
.observer-players .player-two { border-color: var(--p2); }
|
||||
.observer-players .player-two { border-color: var(--p2); border-style: dashed; }
|
||||
.observer-players svg { width: 16px; height: 16px; vertical-align: middle; }
|
||||
.zoom-controls { display: flex; gap: 5px; }
|
||||
.zoom-controls button { min-width: 42px; min-height: 38px; border: 1px solid var(--aqua); background: #102a30; color: var(--aqua); cursor: pointer; }
|
||||
.observer-stage { position: absolute; inset: 92px 0 0; overflow: hidden; border: 2px solid #2f2041; background: #050408; cursor: grab; touch-action: none; user-select: none; }
|
||||
@@ -213,10 +220,9 @@ input:focus { border-color: var(--aqua); box-shadow: 0 0 0 3px #43ead229; }
|
||||
.observer-maze .object { inset: 5%; }
|
||||
.observer-maze .object-key::after { display: none; }
|
||||
.observer-maze .object-landmark { font-size: 6px; }
|
||||
.observer-maze .pawn { inset: -30%; z-index: 5; font-size: 5px; border-width: 1px; box-shadow: 1px 1px 0 #000; }
|
||||
.observer-maze .pawn { inset: -30%; z-index: 5; padding: 1px; border-width: 1px; box-shadow: 1px 1px 0 #000; }
|
||||
.observer-maze .pawn-player-one { color: var(--p1); }
|
||||
.observer-maze .pawn-player-two { color: var(--p2); transform: rotate(45deg); }
|
||||
.observer-maze .pawn-player-two span { transform: rotate(-45deg); }
|
||||
.observer-maze .pawn-player-two { color: var(--p2); }
|
||||
.observer-requirement { display: none; position: absolute; inset: 92px 0 0; place-content: center; justify-items: center; gap: 12px; padding: 24px; color: var(--muted); text-align: center; border: 2px solid var(--edge); background: var(--panel-deep); }
|
||||
.observer-requirement svg { width: 58px; height: 58px; color: var(--p2); }
|
||||
.observer-requirement strong { color: var(--ink); font-size: 1.2rem; text-transform: uppercase; }
|
||||
|
||||
+19
-23
@@ -19,11 +19,11 @@
|
||||
const ONBOARDING_STEPS = Object.freeze([
|
||||
Object.freeze({icon: "hand", title: "Потяни героя", text: "Потяни героя — лабиринт поедет под ним."}),
|
||||
Object.freeze({icon: "motion", title: "Отпусти — герой ждёт", text: "Рука остановилась — шаги тоже."}),
|
||||
Object.freeze({icon: "maze", title: "Стена не пускает", text: "Упрись в стену — попробуй другую сторону."}),
|
||||
Object.freeze({icon: "spikes", title: "Стена не пускает", text: "Упрись в стену — попробуй другую сторону."}),
|
||||
Object.freeze({icon: "key", title: "Найди ключ", text: "Ключ открывает твой выход."}),
|
||||
Object.freeze({icon: "eye", title: "Глаз расширяет карту", text: "Возьми глаз — увидишь дальше."}),
|
||||
Object.freeze({icon: "crystal", title: "Кристалл расширяет карту", text: "Возьми кристалл — увидишь дальше."}),
|
||||
Object.freeze({icon: "arrow", title: "Стрелка знает путь", text: "Компас показывает сторону выхода."}),
|
||||
Object.freeze({icon: "exit", title: "Выходи с ключом", text: "Коснись своего выхода с ключом."}),
|
||||
Object.freeze({icon: "exit-closed", title: "Выходи с ключом", text: "Коснись своего выхода с ключом."}),
|
||||
Object.freeze({icon: "friends", title: "Встретил друга", text: "Другой герой может отдать найденный ключ."})
|
||||
]);
|
||||
const NAME_ADJECTIVES = Object.freeze(["Бодрый", "Хитрый", "Тихий", "Смелый", "Ловкий", "Сонный", "Шустрый", "Лунный", "Яркий", "Дикий", "Вечный", "Храбрый"]);
|
||||
@@ -421,29 +421,28 @@
|
||||
function addObject(cell, object) {
|
||||
const [kind, owner, variant] = object;
|
||||
let marker;
|
||||
const ownerVariant = owner === 1 ? "two" : "one";
|
||||
if (kind === "key") {
|
||||
marker = icon("key", "object object-key"); marker.dataset.owner = owner === 1 ? "two" : "one";
|
||||
marker = icon("key", "object object-key"); marker.dataset.owner = ownerVariant;
|
||||
} else if (kind === "exit") {
|
||||
marker = documentObject.createElement("span"); marker.className = "object object-exit"; marker.dataset.owner = owner === 1 ? "two" : "one"; marker.dataset.open = String(variant);
|
||||
marker = icon(variant ? "exit-open" : "exit-closed", "object object-exit"); marker.dataset.owner = ownerVariant; marker.dataset.open = String(variant);
|
||||
} else if (kind === "compass") {
|
||||
marker = icon("arrow", "object object-compass");
|
||||
} else if (kind === "vision") {
|
||||
marker = icon("eye", "object object-vision");
|
||||
marker = icon("crystal", "object object-vision");
|
||||
} else {
|
||||
marker = documentObject.createElement("span"); marker.className = "object object-landmark"; marker.textContent = ["♣", "✿", "♠", "⌂"][variant % 4] || "♣";
|
||||
}
|
||||
cell.appendChild(marker);
|
||||
}
|
||||
|
||||
function addPawn(cell, label, opponent) {
|
||||
const pawn = documentObject.createElement("span");
|
||||
pawn.className = opponent ? "pawn pawn-opponent" : "pawn pawn-self";
|
||||
const text = documentObject.createElement("span"); text.textContent = label;
|
||||
pawn.appendChild(text); cell.appendChild(pawn);
|
||||
function addPawn(cell, slot, opponent) {
|
||||
const pawn = icon("player", `pawn ${opponent ? "pawn-opponent" : "pawn-self"} pawn-owner-${slot === 2 ? "two" : "one"}`);
|
||||
cell.appendChild(pawn);
|
||||
}
|
||||
|
||||
function characterForSlot(slot) {
|
||||
return slot === 2 ? {symbol: "◆", name: "Мышонок"} : {symbol: "●", name: "Лисёнок"};
|
||||
return slot === 2 ? {name: "Синий герой", keyName: "синего игрока", owner: "two"} : {name: "Жёлтый герой", keyName: "жёлтого игрока", owner: "one"};
|
||||
}
|
||||
|
||||
function renderMap(snapshot) {
|
||||
@@ -477,7 +476,7 @@
|
||||
if (!(data.passages & 8)) cell.classList.add("wall-w");
|
||||
(objects.get(data.index) || []).forEach(object => addObject(cell, object));
|
||||
const slot = state.session ? state.session.slot : 1;
|
||||
if (data.index === snapshot.opponent) addPawn(cell, characterForSlot(slot === 1 ? 2 : 1).symbol, true);
|
||||
if (data.index === snapshot.opponent) addPawn(cell, slot === 1 ? 2 : 1, true);
|
||||
maze.appendChild(cell);
|
||||
});
|
||||
byId("map-placeholder").hidden = true;
|
||||
@@ -492,15 +491,13 @@
|
||||
setScreen("match");
|
||||
const slot = state.session && state.session.slot ? state.session.slot : 1;
|
||||
const character = characterForSlot(slot);
|
||||
byId("self-slot").textContent = character.symbol;
|
||||
byId("self-slot").setAttribute("aria-label", character.name);
|
||||
byId("avatar-handle-symbol").textContent = character.symbol;
|
||||
byId("avatar-control").setAttribute("aria-label", `Тянуть героя ${character.name} для движения`);
|
||||
byId("self-name").textContent = playerName();
|
||||
byId("identity").className = `identity player-${slot === 1 ? "one" : "two"}`;
|
||||
byId("match-screen").classList.toggle("player-two-mode", slot === 2);
|
||||
byId("match-state").textContent = PHASES[snapshot.phase] || "Гонка идёт";
|
||||
byId("inventory").textContent = snapshot.inventory == null ? "Пусто" : `Ключ ${characterForSlot(snapshot.inventory + 1).symbol}`;
|
||||
byId("inventory").textContent = snapshot.inventory == null ? "Пусто" : `Ключ ${characterForSlot(snapshot.inventory + 1).keyName}`;
|
||||
byId("inventory-stat").dataset.filled = String(snapshot.inventory != null);
|
||||
byId("drop-key").disabled = snapshot.inventory == null;
|
||||
setDirectionalPadEnabled(snapshot.phase === "active" && state.socket && state.socket.readyState === windowObject.WebSocket.OPEN);
|
||||
@@ -561,10 +558,8 @@
|
||||
(objects.get(index) || []).forEach(object => addObject(cell, object));
|
||||
players.forEach((player, playerIndex) => {
|
||||
if (player.cell === index && !player.exited) {
|
||||
const pawn = documentObject.createElement("span");
|
||||
pawn.className = `pawn pawn-player-${playerIndex === 0 ? "one" : "two"}`;
|
||||
const label = documentObject.createElement("span"); label.textContent = `P${playerIndex + 1}`;
|
||||
pawn.appendChild(label); cell.appendChild(pawn);
|
||||
const pawn = icon("player", `pawn pawn-player-${playerIndex === 0 ? "one" : "two"} pawn-owner-${playerIndex === 0 ? "one" : "two"}`);
|
||||
cell.appendChild(pawn);
|
||||
}
|
||||
});
|
||||
maze.appendChild(cell);
|
||||
@@ -582,7 +577,8 @@
|
||||
else if (snapshot.phase !== "paused") hideModal();
|
||||
}
|
||||
|
||||
function showModal(title, text, kicker, countdown) {
|
||||
function showModal(title, text, kicker, countdown, iconName) {
|
||||
byId("modal-icon").querySelector("use").setAttribute("href", `/icons.svg#${iconName || "hourglass"}`);
|
||||
byId("modal-title").textContent = title; byId("modal-text").textContent = text; byId("modal-kicker").textContent = kicker;
|
||||
byId("countdown").hidden = !countdown; byId("modal").hidden = false;
|
||||
}
|
||||
@@ -616,7 +612,7 @@
|
||||
title = slot ? (winner === slot ? "Победа!" : "Победил соперник") : `Победил Игрок ${winner}`;
|
||||
text = slot ? (winner === slot ? "Вы официально быстрее очень большого куста." : "Соперник нашёл выход. Лабиринт требует реванш.") : "Зрительный зал фиксирует героический побег без вмешательства в результат.";
|
||||
}
|
||||
showModal(title, text, "ГОНКА ЗАВЕРШЕНА", false);
|
||||
showModal(title, text, "ГОНКА ЗАВЕРШЕНА", false, "exit-open");
|
||||
resetPlayerControls();
|
||||
byId("modal-action").textContent = state.session && state.session.role === "player" && state.session.slot === 1 ? "Новая гонка" : "Вернуться в лобби";
|
||||
byId("modal-action").hidden = false;
|
||||
@@ -658,7 +654,7 @@
|
||||
} else if (message.type === "error") {
|
||||
state.moveFlow.complete(message.requestId);
|
||||
if (message.payload.code === "invalid_token") { windowObject.localStorage.removeItem(TOKEN_KEY); state.session = null; setScreen("welcome"); }
|
||||
if (message.payload.code === "blocked") { const bump = byId("wall-bump"); bump.textContent = localError("blocked"); bump.classList.remove("show"); void bump.offsetWidth; bump.classList.add("show"); }
|
||||
if (message.payload.code === "blocked") { const bump = byId("wall-bump"); bump.querySelector(".wall-bump-text").textContent = localError("blocked"); bump.classList.remove("show"); void bump.offsetWidth; bump.classList.add("show"); }
|
||||
showNotice(localError(message.payload.code), "error"); feedback(false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user