fix: stabilize ranked lobby layout and pin online count
This commit is contained in:
@@ -6,6 +6,7 @@ body.player-app .app > footer.app-footer {
|
||||
width: 100vw;
|
||||
align-self: center;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
|
||||
@@ -175,4 +175,3 @@ body.player-app:is([data-screen=home], [data-screen=join]) #reconnect { position
|
||||
body.player-app .home-actions > .primary { min-height: 68px; font-size: 28px; }
|
||||
body.player-app .home .stat-mode > div { gap: 16px; }
|
||||
}
|
||||
.online-count { position: absolute; top: max(16px, env(safe-area-inset-top)); right: max(20px, env(safe-area-inset-right)); z-index: 5; color: #45534a; font-size: 13px; font-variant-numeric: tabular-nums; }
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<html lang="ko">
|
||||
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"><meta name="theme-color" content="#ffffff"><title>쎄쎄쎄:기공대전</title></head>
|
||||
<body class="player-app" data-screen="home">
|
||||
<span id="online-count" class="online-count" role="status">접속 확인 중</span>
|
||||
<div class="app">
|
||||
<main id="main">
|
||||
<section id="home" class="home">
|
||||
<span id="online-count" class="online-count" role="status">접속 확인 중</span>
|
||||
<div class="home-stage"><h1 aria-label="쎄쎄쎄:기공대전"><span>쎄쎄쎄</span><span>:기공대전</span></h1><div id="home-fighters" aria-hidden="true"></div></div>
|
||||
<div class="home-menu">
|
||||
<div class="home-actions"><button id="bot-start" class="primary" disabled><span>봇과 대전</span><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M4 12h15m-6-6 6 6-6 6" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"/></svg></button><div class="split"><button id="room-create" disabled><span>방 만들기</span><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M12 4v16M4 12h16" stroke="currentColor" stroke-width="2"/></svg></button><button id="join-open" disabled><span>방 참가</span><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M13 4h7v16h-7M3 12h12m-5-5 5 5-5 5" stroke="currentColor" stroke-width="2"/></svg></button></div></div>
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
.lobby-tools { display: flex; gap: 10px; margin-top: 16px; }
|
||||
.ranked-queue-actions { display: flex; justify-content: center; gap: 12px; }
|
||||
.ranked-queue .accept-time { font-size: 28px; font-weight: 700; font-variant-numeric: tabular-nums; }
|
||||
body.player-app[data-screen=ranked-queue] main { padding: 0; }
|
||||
body.player-app .ranked-queue {
|
||||
display: grid;
|
||||
grid-template-rows: minmax(180px, 1fr) auto minmax(180px, 1fr);
|
||||
flex: 1;
|
||||
margin: 0 auto;
|
||||
padding: 32px 0 16px;
|
||||
}
|
||||
.ranked-queue::before { content: ''; }
|
||||
.ranked-queue .queue-fighter { width: min(260px, 26svh, 65vw); }
|
||||
.ranked-queue .queue-details { display: grid; grid-template-rows: 36px 24px 36px 52px; gap: 8px; padding-top: 8px; }
|
||||
.ranked-queue .queue-details > :is(h1, p) { margin: 0; align-self: center; }
|
||||
.ranked-queue .queue-details > p[hidden] { display: block !important; visibility: hidden; }
|
||||
.lobby-tools button { flex: 1; min-width: 0; min-height: 48px; padding: 10px; font-size: 17px; }
|
||||
#home .home-actions > button:not(.primary) { min-height: 64px; font-size: 26px; }
|
||||
#home .home-actions .split { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
|
||||
@@ -29,7 +29,7 @@ export function createLobbyFeatures(options: LobbyOptions) {
|
||||
tools.innerHTML = '<button id="leaderboard-open" disabled>리더보드</button><button id="appearance-open" disabled>캐릭터·장비</button>';
|
||||
actions.after(tools);
|
||||
const queue = document.createElement('section'); queue.id = 'ranked-queue'; queue.className = 'waiting ranked-queue'; queue.hidden = true;
|
||||
queue.innerHTML = '<div class="queue-fighter" aria-hidden="true"></div><h1 role="status">랭킹대전 매칭 중…</h1><p class="accept-status" role="status" hidden></p><p class="accept-time" aria-label="수락 남은 시간" hidden></p><div class="ranked-queue-actions"><button id="ranked-accept" class="primary" hidden>수락</button><button id="ranked-cancel">매칭 취소</button></div>';
|
||||
queue.innerHTML = '<div class="queue-fighter" aria-hidden="true"></div><div class="queue-details"><h1 role="status">랭킹대전 매칭 중…</h1><p class="accept-status" role="status" hidden></p><p class="accept-time" aria-label="수락 남은 시간" hidden></p><div class="ranked-queue-actions"><button id="ranked-accept" class="primary" hidden>수락</button><button id="ranked-cancel">매칭 취소</button></div></div>';
|
||||
document.getElementById('waiting')!.after(queue);
|
||||
const appearanceDialog = document.createElement('dialog'); appearanceDialog.id = 'appearance-dialog';
|
||||
appearanceDialog.innerHTML = '<div class="dialog-top"><h2>캐릭터·장비</h2><button class="quiet" aria-label="캐릭터·장비 닫기">닫기</button></div><div class="appearance-layout"><div class="appearance-preview" aria-label="외형 미리보기"></div><div class="appearance-options"><h3 id="skin-label">피부</h3><div class="skin-options" role="group" aria-labelledby="skin-label"></div><h3 id="hair-label">머리</h3><div class="hair-options" role="group" aria-labelledby="hair-label"></div><h3 id="color-label">도복 색상</h3><div class="color-options" role="group" aria-labelledby="color-label"></div><p class="color-condition" role="status"></p><h3 id="equipment-label">장비</h3><div class="equipment-options" role="group" aria-labelledby="equipment-label"></div><h3 id="eyewear-label" class="eyewear-heading">안경</h3><div class="eyewear-options" role="group" aria-labelledby="eyewear-label"></div><button class="equip primary">장착</button></div></div><p class="profile-notice" role="status"></p>';
|
||||
|
||||
+1
-1
@@ -114,7 +114,7 @@ function showMatch(state: LocalMatchState) {
|
||||
if (currentScreen !== 'result' || state.phase !== 'finished') setScreen('match');
|
||||
if (!view) view = createMatchView(el('match'), action => currentMatch?.ranked ? lobby!.submitAction(action) : controller?.submitAction(action) ?? Promise.resolve(false),
|
||||
() => { void perform(() => currentMatch?.ranked ? lobby!.rematch() : controller ? controller.rematch() : rematch(), 'rematch'); },
|
||||
() => { void perform(leave); }, () => setScreen('result'), footer.openRules, undefined,
|
||||
() => { void perform(leave); }, () => setScreen('result'), undefined,
|
||||
id => currentMatch?.ranked ? lobby!.sendEmote(id) : controller?.sendEmote(id) ?? false);
|
||||
view.update(state);
|
||||
if (state.finished?.saved && !state.ranked) stats(state.finished.stats);
|
||||
|
||||
@@ -14,8 +14,8 @@ const fighter = (i: number, appearance?: FighterAppearance) => `<div class="figh
|
||||
<div class="fighter-caption"><span class="choice-status"></span><span class="action-label"></span><span class="energy-change" aria-hidden="true"></span></div>
|
||||
</div>`;
|
||||
|
||||
export function createMatchView(container: HTMLElement, onAction: (action: ActionId) => Promise<boolean>, onRematch: () => void, onLeave: () => void, onResult: () => void = () => {}, onRules: () => void = () => {}, appearances?: [FighterAppearance, FighterAppearance], onEmote?: (id: EmoteId) => boolean | Promise<boolean>) {
|
||||
container.innerHTML = `<section class="match-introduction waiting" hidden><div class="queue-fighter" aria-hidden="true"></div><h1 role="status"></h1></section><div class="match-top"><div class="match-title"><span class="mode-label"></span><h1 class="turn-heading"></h1></div><details class="match-menu"><summary aria-label="대전 메뉴">메뉴</summary><div class="match-menu-items"><button class="match-rules quiet">규칙</button><button class="leave quiet">나가기</button></div></details></div>
|
||||
export function createMatchView(container: HTMLElement, onAction: (action: ActionId) => Promise<boolean>, onRematch: () => void, onLeave: () => void, onResult: () => void = () => {}, appearances?: [FighterAppearance, FighterAppearance], onEmote?: (id: EmoteId) => boolean | Promise<boolean>) {
|
||||
container.innerHTML = `<section class="match-introduction waiting" hidden><div class="queue-fighter" aria-hidden="true"></div><h1 role="status"></h1></section><div class="match-top"><div class="match-title"><span class="mode-label"></span><h1 class="turn-heading"></h1></div></div>
|
||||
<div class="timing"><span class="selection-status" role="status"></span><span class="timeout-chances" aria-label="남은 초읽기 기회"></span><span class="time-label" aria-label="남은 시간"></span><div class="time-track" role="progressbar" aria-label="남은 선택 시간" aria-valuemin="0" aria-valuemax="100"><span></span></div></div>
|
||||
<div class="arena"><div class="arena-floor" aria-hidden="true"></div><div class="result-banner" hidden><h2></h2><p class="finish-reason"></p></div>${fighter(0, appearances?.[0])}<div class="duel-cue"><span class="versus" aria-hidden="true">VS</span><strong class="countdown-number" role="status"></strong></div><span class="clash-label" role="status"></span><div class="clash-burst" aria-hidden="true"></div>${fighter(1, appearances?.[1])}</div>
|
||||
<div class="player-controls" role="group" aria-label="내 행동 선택"><div class="energy">${actionIcon('charge')}<strong class="energy-count"></strong></div><div class="action-buttons">${ACTION_IDS.map(id => `<button data-action="${id}" aria-label="${ACTIONS[id].name}, ${ACTIONS[id].effect}${ACTIONS[id].cost ? `, 기 ${ACTIONS[id].cost} 소모` : ''}"><span class="action-symbol">${actionIcon(id)}</span><strong>${ACTIONS[id].name}</strong><small>${id === 'charge' || id === 'guard' ? ACTIONS[id].effect : `기 ${ACTIONS[id].cost} 소모`}</small><span class="chosen-mark">${checkIcon}</span></button>`).join('')}</div></div>
|
||||
@@ -76,12 +76,7 @@ export function createMatchView(container: HTMLElement, onAction: (action: Actio
|
||||
if (request === submission) { busy = false; tick(); }
|
||||
}
|
||||
}));
|
||||
el('.leave').onclick = onLeave; el('.home-return').onclick = onLeave; el('.rematch').onclick = onRematch;
|
||||
el('.match-rules').onclick = () => {
|
||||
el<HTMLDetailsElement>('.match-menu').open = false;
|
||||
el('.match-menu summary').focus();
|
||||
onRules();
|
||||
};
|
||||
el('.home-return').onclick = onLeave; el('.rematch').onclick = onRematch;
|
||||
function tick() {
|
||||
if (!current) return;
|
||||
const now = Date.now() + offset, s = current;
|
||||
|
||||
@@ -74,29 +74,6 @@ body:is([data-screen=match], [data-screen=result]) main { padding: 0; min-height
|
||||
#match .time-track span { border-radius: 0; background: #a85039; }
|
||||
#match[data-locked=true] .time-track span { background: #557b85; }
|
||||
#match .timing[data-urgent=true] .time-track span { background: #c04128; }
|
||||
#match .match-menu {
|
||||
pointer-events: auto;
|
||||
position: absolute; top: calc(25px + env(safe-area-inset-top));
|
||||
right: max(var(--edge), env(safe-area-inset-right));
|
||||
}
|
||||
#match .match-menu summary {
|
||||
display: grid; place-items: center; list-style: none; cursor: pointer;
|
||||
min-width: 52px; min-height: 44px; padding: 8px 12px;
|
||||
border: 1px solid #52615655; border-radius: 3px; background: #f4f0e3;
|
||||
font-size: 12px; font-weight: 700;
|
||||
}
|
||||
#match .match-menu summary::-webkit-details-marker { display: none; }
|
||||
#match .match-menu summary:focus-visible { outline: 3px solid #266acc; outline-offset: 4px; }
|
||||
#match .match-menu[open] summary { background: #303c38; color: #fff9e9; }
|
||||
#match .match-menu-items {
|
||||
position: absolute; top: calc(100% + 8px); right: 0; width: 132px;
|
||||
padding: 6px; background: #f4f0e3; border: 1px solid #697568;
|
||||
box-shadow: 3px 4px 0 #303c3820;
|
||||
}
|
||||
#match .match-menu-items button {
|
||||
display: block; width: 100%; min-height: 44px; border: 0; border-radius: 0;
|
||||
color: #303c38; text-align: left;
|
||||
}
|
||||
#match .duel-cue, #match[data-phase=countdown] .duel-cue { top: 43%; }
|
||||
#match:not([data-phase=countdown]) .versus { display: none; }
|
||||
#match[data-phase=countdown] .countdown-number { border-radius: 3px; }
|
||||
@@ -178,7 +155,6 @@ body:is([data-screen=match], [data-screen=result]) #reconnect {
|
||||
#match .fighter { width: 47%; }
|
||||
#match .match-title { top: calc(24px + env(safe-area-inset-top)); left: var(--edge); transform: none; }
|
||||
#match .turn-heading { font-size: 16px; }
|
||||
#match .match-menu { top: calc(12px + env(safe-area-inset-top)); }
|
||||
#match .energy { gap: 3px; }
|
||||
#match .energy-count { font-size: 25px; }
|
||||
#match .energy > svg { width: 18px; height: 18px; }
|
||||
@@ -217,7 +193,6 @@ body:is([data-screen=match], [data-screen=result]) #reconnect {
|
||||
#match[data-phase=finished] { --stage-top: calc(96px + env(safe-area-inset-top)); }
|
||||
#match .arena { padding-inline: 12vw; }
|
||||
#match .match-title { top: calc(12px + env(safe-area-inset-top)); left: 50%; transform: translateX(-50%); }
|
||||
#match .match-menu { top: calc(10px + env(safe-area-inset-top)); right: max(12px, env(safe-area-inset-right)); }
|
||||
#match .timing { top: calc(38px + env(safe-area-inset-top)); width: 120px; min-height: 33px; flex-direction: row; padding-bottom: 8px; }
|
||||
#match .player-controls { width: calc(50% - 24px); margin-bottom: max(8px, env(safe-area-inset-bottom)); }
|
||||
#match .energy { padding-bottom: 4px; }
|
||||
|
||||
+15
-9
@@ -1,4 +1,15 @@
|
||||
/* Import after game.css, match.css and home.css. */
|
||||
body.player-app > .online-count {
|
||||
position: fixed;
|
||||
top: max(16px, env(safe-area-inset-top));
|
||||
right: max(20px, env(safe-area-inset-right));
|
||||
z-index: 50;
|
||||
color: #45534a;
|
||||
font-size: 12px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
}
|
||||
body.player-app #match .emote-trigger,
|
||||
body.player-app #match .emote-trigger:hover:not(:disabled),
|
||||
body.player-app #match .emote-trigger:active:not(:disabled) {
|
||||
@@ -34,8 +45,7 @@ body.player-app #match .action-buttons strong {
|
||||
}
|
||||
|
||||
body.player-app button,
|
||||
body.player-app #match button,
|
||||
body.player-app #match .match-menu summary {
|
||||
body.player-app #match button {
|
||||
border: 2px solid var(--ink);
|
||||
border-radius: 0;
|
||||
background: var(--ink);
|
||||
@@ -54,8 +64,7 @@ body.player-app #match .action-buttons button[data-action=charge] {
|
||||
}
|
||||
|
||||
body.player-app button:hover:not(:disabled),
|
||||
body.player-app #match button:hover:not(:disabled),
|
||||
body.player-app #match .match-menu summary:hover {
|
||||
body.player-app #match button:hover:not(:disabled) {
|
||||
background: #ffbd43;
|
||||
border-color: var(--ink);
|
||||
color: var(--ink);
|
||||
@@ -63,8 +72,7 @@ body.player-app #match .match-menu summary:hover {
|
||||
}
|
||||
|
||||
body.player-app button:active:not(:disabled),
|
||||
body.player-app #match button:active:not(:disabled),
|
||||
body.player-app #match .match-menu summary:active {
|
||||
body.player-app #match button:active:not(:disabled) {
|
||||
border-width: 2px;
|
||||
box-shadow: none;
|
||||
transform: translate(3px, 3px);
|
||||
@@ -113,8 +121,6 @@ body.player-app #match .action-buttons small { color: inherit; font-family: var(
|
||||
body.player-app #match .action-buttons .action-symbol { color: inherit; opacity: 1; filter: none; }
|
||||
body.player-app #match .energy > svg { color: var(--ink); }
|
||||
body.player-app #match .chosen-mark { background: var(--ink); border-color: #fff; color: #fff; border-radius: 0; }
|
||||
body.player-app #match .match-menu-items { background: #fff; border: 2px solid var(--ink); border-radius: 0; box-shadow: 4px 4px 0 #24252640; padding: 8px; }
|
||||
body.player-app #match .match-menu-items button + button { margin-top: 8px; }
|
||||
|
||||
body.player-app dialog:not(.finish-cutscene) {
|
||||
padding: 24px;
|
||||
@@ -136,5 +142,5 @@ body.player-app #contact-content { overflow-wrap: anywhere; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
body.player-app button, body.player-app #match button, body.player-app #match .match-menu summary { transition: none; }
|
||||
body.player-app button, body.player-app #match button { transition: none; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user