fix: mobile team card width and modularize CSS with documentation
This commit is contained in:
parent
43383bb833
commit
36fd25731a
15
agent.md
15
agent.md
|
|
@ -29,6 +29,10 @@
|
|||
│ └── visitors.js # 유니크 방문자 체크 및 통계 API
|
||||
├── public/ # 정적 리소스 (게임 에셋)
|
||||
│ └── assets/
|
||||
│ ├── effects/ # 공통 전투/월드 이펙트 스프라이트시트
|
||||
│ │ ├── heal/ # 처치 회복 연출
|
||||
│ │ ├── world_Effect.png # 화염 메테오 7프레임 이미지
|
||||
│ │ └── world_Effect_2.png # 냉기 메테오 7프레임 이미지
|
||||
│ └── characters/ # 20종 이상의 캐릭터 스킨 및 투사체 에셋
|
||||
│ ├── archer/, armored-axeman/, armored-orc/, ... (중략)
|
||||
│ └── wizard/ # 각 폴더 내 애니메이션 시트 및 이펙트 포함
|
||||
|
|
@ -39,19 +43,21 @@
|
|||
├── game/ # 게임 로직 모듈 (역할별 하위 폴더 구성)
|
||||
│ ├── arena/ # 아레나 및 씬 관리
|
||||
│ │ ├── ArenaScene.js # 메인 게임 씬 (Orchestrator, 생명주기 및 모듈 조율)
|
||||
│ │ ├── arenaRenderer.js# 경기장 바닥 및 격자 렌더링
|
||||
│ │ ├── arenaRenderer.js# 경기장 바닥, 격자 및 팀 시작 영역 렌더링
|
||||
│ │ └── arenaSpectatorCamera.js # 지능형 관전 카메라 및 줌 로직
|
||||
│ ├── combat/ # 전투 시스템
|
||||
│ │ ├── combat.js # 전투 AI, 투사체 및 피격 판정 핵심 엔진
|
||||
│ │ ├── combatSettings.js # 전투 속도 및 이동 배율 관리
|
||||
│ │ └── arenaFinalCombatEffects.js # 최종 교전 슬로우 모션 등 연출 효과
|
||||
│ │ ├── arenaFinalCombatEffects.js # 최종 교전 슬로우 모션 등 연출 효과
|
||||
│ │ └── worldEffects.js # 주기적 메테오/냉각지대 및 냉기 동결 효과
|
||||
│ ├── fighter/ # 캐릭터 및 에셋
|
||||
│ │ ├── fighterAssets.js # 스프라이트 로드 및 팀 실루엣 동적 생성
|
||||
│ │ ├── fighterFactory.js # 캐릭터 인스턴스화 및 HUD 동기화
|
||||
│ │ ├── fighterManifest.js # 20종 캐릭터 스탯/특성 상세 정의
|
||||
│ │ ├── fighterStats.js # 근접/원거리/마법 프로필 판별 및 스탯 해석
|
||||
│ │ └── fighterSelection.js # 캐릭터 스킨 무작위 선택 로직
|
||||
│ └── match/ # 매치 및 진행
|
||||
│ ├── matchSetup.js # 팀 구성 및 스폰 좌표 계산 (구역/랜덤)
|
||||
│ ├── matchSetup.js # 팀 구성 및 스폰 좌표 계산 (스타팅 영역/랜덤)
|
||||
│ └── arenaMatchRuntime.js # 매치 진행 중 헬퍼 (스폰 클러스터, 팀 크기 동기화)
|
||||
└── ui/ # UI 컴포넌트 및 API 연동
|
||||
├── arenaKillLog.js # [New] 독립된 킬로그 DOM 조작 모듈
|
||||
|
|
@ -71,9 +77,10 @@
|
|||
- **[인프라 및 전역 설정] [context/core.md](./context/core.md)**: `main.js`, `constants.js`, 개발/유지보수 공통 규칙.
|
||||
- **[서버 및 API] [context/server.md](./context/server.md)**: Fastify 서버, MongoDB 연동, 방문자 및 사망 통계 API 상세.
|
||||
- **[아레나 및 카메라] [context/arena.md](./context/arena.md)**: `ArenaScene` 오케스트레이션, 지능형 카메라 추적, 미니맵 가이드라인.
|
||||
- **[전투 엔진] [context/combat.md](./context/combat.md)**: 전투 AI, 투사체 판정, 처치 보상 성장, 슬로우모션 연출.
|
||||
- **[전투 엔진] [context/combat.md](./context/combat.md)**: 전투 AI, 투사체 판정, 처치 보상 성장, 슬로우모션 및 월드 이펙트 연출.
|
||||
- **[캐릭터 및 에셋] [context/fighter.md](./context/fighter.md)**: 캐릭터 공장, 동적 실루엣 생성, 종족 및 특성(Slime 등) 정의.
|
||||
- **[매치 로직 및 UI] [context/match-ui.md](./context/match-ui.md)**: 팀 구성 및 스폰 알고리즘, HUD 레이아웃, 킬로그, 승리 연출 UI.
|
||||
- **[스타일 및 디자인] [context/style.md](./context/style.md)**: CSS 모듈 구조, 디자인 변수, 반응형 및 애니메이션 가이드.
|
||||
|
||||
## 4. 기술 사양
|
||||
|
||||
|
|
|
|||
|
|
@ -18,14 +18,15 @@
|
|||
|
||||
### 매치 설정 및 스폰 배치
|
||||
- **완전 랜덤 배치**: 전장 전체 스폰 슬롯을 무작위로 섞어 배치합니다.
|
||||
- **스타팅 지점 배치**: 참가자 수에 맞춰 전장을 구역으로 나눈 뒤, 참가자별 구역 배정을 매치마다 섞고 구역 내 무작위 위치에 스폰합니다.
|
||||
- **스타팅 지점 배치**: 팀마다 전장 스폰 가능 그리드에서 중심 셀을 무작위로 고르고, 중심 주변 2칸(`5 x 5`)을 해당 팀의 스타팅 영역으로 사용합니다. 겹치지 않는 후보가 남아 있는 동안에는 해당 후보를 우선 선택하며, 영역은 매치 시작 후 5초 동안만 팀 색상으로 매우 옅게 표시되고 팀 전투원은 이 안에서만 스폰합니다.
|
||||
- **설정 유지**: 닉네임, 인원, 배치 모드는 `localStorage`에 저장되어 재접속 시 복원됩니다.
|
||||
|
||||
### 전투 화면 레이아웃 (HUD)
|
||||
- **팀 Badge**: 좌측 HUD 레일에 배치되며, 클릭 시 해당 팀의 생존 유닛 중 무작위 1명으로 시점을 고정합니다.
|
||||
- **킬로그**: 처치자와 피처치자를 좌우로 배치하고, 피처치자 아이콘에 빨간 X를 겹쳐 사망 관계를 명확히 표시합니다.
|
||||
- **팀 Badge 갱신 안정성**: 사망으로 생존 수가 바뀔 때 기존 badge 버튼 DOM을 유지한 채 숫자, 비활성 상태, 선택 강조만 갱신하여 사망 프레임에 겹친 클릭도 시점 고정으로 전달되도록 합니다.
|
||||
- **킬로그**: 처치자와 피처치자를 좌우로 배치하고, 피처치자 아이콘에 빨간 X를 겹쳐 사망 관계를 명확히 표시합니다. 캐릭터 idle 시트의 `100x100` 프레임 내 투명 여백을 제외한 중앙 하단 영역을 확대 표시해 작은 아이콘 박스에서도 실루엣이 충분히 보이도록 합니다.
|
||||
- **하단 메타 정보**: 전투 화면 우측 하단(`arena-meta` 컨테이너)에 방문자 카운터와 About 버튼이 Pill(알약) 형태로 디자인이 통일되어 나란히 고정 배치됩니다. 드로어가 열려도 동일한 위치를 유지합니다.
|
||||
- **모바일 레이아웃**: 실제 전투 시작 시 모바일에서는 옵션 drawer를 자동으로 접고, 상단 팀 HUD는 옵션 버튼 폭을 제외한 영역에 두 줄 4열로 맞춰 4개 이후 팀도 잘리지 않게 합니다. 모바일 팀 카드 선택 표시는 내부 테두리로 처리해 외곽선이 잘려 보이지 않게 합니다. 킬로그는 전투 캔버스 바로 아래에 배치하되 하단 메타 정보(방문자 카운터/About)와 겹치지 않게 안전 여백을 확보합니다.
|
||||
- **모바일 레이아웃**: 실제 전투 시작 시 모바일에서는 옵션 drawer를 자동으로 접고, 상단 팀 HUD는 옵션 버튼 폭을 제외한 영역에 두 줄로 배치됩니다. 이때 데스크톱의 고정 가로폭 상속을 방지(`grid-template-columns: none`)하여 모든 팀 카드가 균일한 가로폭을 유지하도록 하며, 4개 이후 팀도 스크롤을 통해 확인할 수 있습니다. 모바일 팀 카드 선택 표시는 내부 테두리로 처리해 외곽선이 잘려 보이지 않게 합니다. 킬로그는 전투 캔버스 바로 아래에 배치하되 하단 메타 정보(방문자 카운터/About)와 겹치지 않게 안전 여백을 확보합니다.
|
||||
- **모바일 옵션 drawer**: 전투 중 펼친 옵션 drawer는 닉네임 입력 높이와 컨트롤 간격을 줄여 전투 시작/재시작/일시정지 버튼이 작은 화면에서도 한 번에 보이도록 합니다.
|
||||
- **승리 연출**: 승리 시 Web Audio 기반 팡파르와 CSS 애니메이션(광선, 컨페티)을 결합해 화려하게 연출합니다. 전투 종료 시 옵션 drawer를 접어 결과 배너가 설정 폼과 충돌하지 않게 하며, 결과 배너는 일정 시간 후 자동으로 사라지거나 클릭 시 즉시 닫힙니다. 무승부는 더 차분한 톤을 사용합니다.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
# Context: Style & Design
|
||||
|
||||
## 1. CSS 모듈 구조 (src/styles/)
|
||||
|
||||
이 프로젝트는 거대한 단일 CSS 파일을 지양하고, 기능별로 분리된 모듈형 CSS 구조를 채택하고 있습니다. `src/styles.css`는 각 모듈을 통합하는 엔트리 포인트 역할을 합니다.
|
||||
|
||||
- **`base.css`**: 전역 변수(`:root`), 리셋 스타일, 레이아웃의 뼈대(`#app`, `#game`, `.arena-shell`)를 정의합니다.
|
||||
- **`intro.css`**: 대기 화면, 로고 애니메이션, 전투 프리뷰 연출 스타일을 담당합니다.
|
||||
- **`game-ui.css`**: 스코어보드(팀 badge), 킬로그, 상단 전투 안내바, 승리/무승부 축하 레이어 등 실제 게임 진행 중 노출되는 모든 HUD 요소를 관리합니다.
|
||||
- **`overlay.css`**: 설정 드로어(전투 옵션 폼), About 다이얼로그 및 공통 폼 컨트롤 스타일을 정의합니다.
|
||||
- **`animations.css`**: 프로젝트 전역에서 재사용되는 `@keyframes`와 애니메이션 관련 유틸리티 클래스를 포함합니다.
|
||||
- **`mobile.css`**: `960px` 이하 해상도를 위한 미디어 쿼리 오버라이드 스타일을 통합 관리합니다. 모바일 전용 레이아웃 조정 및 터치 최적화 스타일이 포함됩니다.
|
||||
|
||||
## 2. 디자인 시스템 및 변수
|
||||
|
||||
- **색상 체계**: 어두운 배경(`#080a07`)과 금색/주황색 계열의 포인트 컬러(`rgb(238 185 73)`)를 사용하여 판타지 아레나 분위기를 연출합니다.
|
||||
- **반응형 대응**: `clamp()`, `min()`, `calc()` 등 현대적인 CSS 함수를 적극 활용하여 다양한 화면 크기에서도 유연하게 대응합니다.
|
||||
- **가독성**: 텍스트 섀도우와 반투명 배경(`backdrop-filter`)을 활용해 복잡한 전투 화면 위에서도 UI 요소의 시인성을 확보합니다.
|
||||
|
||||
## 3. 스타일 수정 가이드
|
||||
|
||||
- **전역 상수 변경**: 색상이나 기본 여백 등은 `base.css`의 `:root` 변수를 먼저 확인하십시오.
|
||||
- **컴포넌트 스타일 수정**: 수정하려는 UI 요소가 속한 카테고리에 맞는 파일을 열어 작업하십시오. (예: 킬로그 수정 -> `game-ui.css`)
|
||||
- **모바일 레이아웃 수정**: 데스크톱 스타일을 수정한 후에는 `mobile.css`에서 해당 요소가 모바일에서 어떻게 보이는지 반드시 확인하고 필요한 경우 오버라이드하십시오.
|
||||
- **애니메이션 추가**: 새로운 키프레임은 `animations.css`에 추가하여 중앙 집중식으로 관리합니다.
|
||||
1975
src/styles.css
1975
src/styles.css
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,158 @@
|
|||
@keyframes intro-rise {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(22px) scale(0.96);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes preview-attack {
|
||||
to {
|
||||
background-position-x: var(--sprite-end);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes preview-breathe {
|
||||
0%,
|
||||
100% {
|
||||
margin-top: 0;
|
||||
}
|
||||
50% {
|
||||
margin-top: -8px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes preview-strike {
|
||||
0%,
|
||||
58%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
64%,
|
||||
76% {
|
||||
opacity: 0.86;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes status-marquee {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes kill-log-entry {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes banner-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(18px) scale(0.78);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes victory-banner-sheen {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(0) skewX(-18deg);
|
||||
}
|
||||
18% {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateX(560%) skewX(-18deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes victory-confetti-burst {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%) rotate(var(--confetti-tilt)) scale(0.3);
|
||||
}
|
||||
12% {
|
||||
opacity: 1;
|
||||
}
|
||||
74% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform:
|
||||
translate(calc(-50% + var(--confetti-x)), calc(-50% + var(--confetti-y)))
|
||||
rotate(calc(var(--confetti-tilt) + var(--confetti-spin)))
|
||||
scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes victory-glow {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.58);
|
||||
}
|
||||
35% {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0.8;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes victory-rays-in {
|
||||
from {
|
||||
transform: scale(0.56);
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes victory-rays-turn {
|
||||
to {
|
||||
rotate: 360deg;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes victory-message-pulse {
|
||||
from {
|
||||
opacity: 0.72;
|
||||
transform: scale(0.88);
|
||||
}
|
||||
58% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.victory-banner,
|
||||
.victory-banner::before,
|
||||
.victory-banner-message,
|
||||
.victory-celebration::before,
|
||||
.victory-confetti-piece,
|
||||
.victory-rays {
|
||||
animation-duration: 1ms;
|
||||
animation-iteration-count: 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
:root {
|
||||
color-scheme: dark;
|
||||
font-family:
|
||||
Inter, Pretendard, "Noto Sans KR", system-ui, -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", sans-serif;
|
||||
background: #080a07;
|
||||
color: #fff5db;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
min-width: 320px;
|
||||
min-height: 100%;
|
||||
background: #080a07;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:focus-visible,
|
||||
input:focus-visible,
|
||||
textarea:focus-visible {
|
||||
outline: 3px solid rgb(238 185 73 / 0.46);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
#app {
|
||||
--arena-gap: 18px;
|
||||
--score-band-height: 134px;
|
||||
--score-panel-left: 14px;
|
||||
--score-panel-width: 260px;
|
||||
--score-rail-width: calc(var(--score-panel-left) + var(--score-panel-width));
|
||||
--drawer-width: min(430px, 100vw);
|
||||
--drawer-live-width: min(340px, calc(100vw - 48px));
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(180deg, rgb(8 10 7 / 0.18), rgb(3 5 4 / 0.84)),
|
||||
#080a07;
|
||||
}
|
||||
|
||||
#app.match-live {
|
||||
--drawer-width: var(--drawer-live-width);
|
||||
}
|
||||
|
||||
.arena-shell {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
overflow: hidden;
|
||||
background: #090b08;
|
||||
}
|
||||
|
||||
.arena-shell::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
background:
|
||||
radial-gradient(circle at 50% 50%, rgb(255 211 122 / 0.06), transparent 42%),
|
||||
linear-gradient(90deg, rgb(3 5 4 / 0.48), rgb(3 5 4 / 0.08) 45%, rgb(3 5 4 / 0.48)),
|
||||
linear-gradient(180deg, rgb(3 5 4 / 0.08), rgb(3 5 4 / 0.5));
|
||||
pointer-events: none;
|
||||
transition:
|
||||
background 520ms ease,
|
||||
opacity 520ms ease;
|
||||
}
|
||||
|
||||
#app.match-live .arena-shell::before {
|
||||
opacity: 0.24;
|
||||
}
|
||||
|
||||
#app.match-live .arena-shell {
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
#game {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
width: max(100vw, 100vh);
|
||||
height: max(100vw, 100vh);
|
||||
overflow: hidden;
|
||||
opacity: 0.68;
|
||||
filter: saturate(1) contrast(1.08) brightness(1.08);
|
||||
transform: scale(1.04);
|
||||
transform-origin: center;
|
||||
transition:
|
||||
width 620ms cubic-bezier(0.2, 0.8, 0.2, 1),
|
||||
height 620ms cubic-bezier(0.2, 0.8, 0.2, 1),
|
||||
opacity 520ms ease,
|
||||
filter 520ms ease,
|
||||
transform 700ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
|
||||
#app.match-live #game {
|
||||
width: min(100vw, 100vh);
|
||||
height: min(100vw, 100vh);
|
||||
margin-left: 0;
|
||||
opacity: 1;
|
||||
filter: none;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
#game canvas {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
|
@ -0,0 +1,601 @@
|
|||
.scoreboard {
|
||||
position: fixed;
|
||||
top: clamp(14px, 3vw, 28px);
|
||||
left: var(--score-panel-left);
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
width: var(--score-panel-width);
|
||||
max-height: calc(100vh - 420px);
|
||||
min-height: 64px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgb(238 185 73 / 0.3) transparent;
|
||||
padding: 8px;
|
||||
border: 1px solid rgb(238 185 73 / 0.18);
|
||||
border-radius: 8px;
|
||||
background: rgb(4 6 4 / 0.5);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transform: translateY(-18px);
|
||||
transition:
|
||||
opacity 420ms ease,
|
||||
transform 420ms ease;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.scoreboard::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.scoreboard::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.scoreboard::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
background: rgb(238 185 73 / 0.3);
|
||||
}
|
||||
|
||||
#app.match-live .scoreboard {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.score-side {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 114px);
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.score-side.right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.team-score {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 1px auto;
|
||||
gap: 6px;
|
||||
width: 114px;
|
||||
min-height: 72px;
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
padding: 8px 9px;
|
||||
color: #fff;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 900;
|
||||
text-align: left;
|
||||
text-shadow: 1px 1px 2px #000;
|
||||
transition:
|
||||
filter 160ms ease,
|
||||
transform 160ms ease;
|
||||
}
|
||||
|
||||
.team-score:hover {
|
||||
filter: brightness(1.16);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.team-score.is-focused {
|
||||
box-shadow:
|
||||
inset 0 0 0 2px rgb(255 244 209 / 0.92),
|
||||
0 0 18px rgb(227 178 79 / 0.26);
|
||||
}
|
||||
|
||||
.team-score:disabled {
|
||||
cursor: default;
|
||||
filter: grayscale(0.6) brightness(0.68);
|
||||
}
|
||||
|
||||
.team-score:disabled:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.team-score-name {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.team-score-rule {
|
||||
width: 100%;
|
||||
background: var(--team-color);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.team-score-count {
|
||||
justify-self: end;
|
||||
color: #fff2c8;
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.battle-notice {
|
||||
position: fixed;
|
||||
top: clamp(12px, 2vw, 20px);
|
||||
left: 50%;
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: min(420px, 72vmin, calc(100vw - 64px));
|
||||
min-height: 38px;
|
||||
border: 1px solid rgb(238 185 73 / 0.26);
|
||||
border-radius: 8px;
|
||||
padding: 8px 14px;
|
||||
background: rgb(8 10 7 / 0.68);
|
||||
color: #ffe8b4;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 900;
|
||||
line-height: 1.35;
|
||||
text-align: center;
|
||||
text-shadow: 1px 1px 2px #000;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, -10px);
|
||||
transition:
|
||||
opacity 260ms ease,
|
||||
transform 260ms ease;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
#app.match-live .battle-notice.is-visible {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
@media (min-width: 961px) {
|
||||
#app.match-live .battle-notice {
|
||||
right: auto;
|
||||
left: 50%;
|
||||
width: min(420px, 72vmin, calc(100vw - var(--drawer-width) - var(--score-rail-width) - 56px));
|
||||
transform: translate(-50%, -10px);
|
||||
}
|
||||
|
||||
#app.match-live .battle-notice.is-visible {
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
#app.match-live.drawer-collapsed .battle-notice {
|
||||
right: auto;
|
||||
width: min(420px, 72vmin, calc(100vw - 64px));
|
||||
}
|
||||
}
|
||||
|
||||
.kill-log {
|
||||
position: fixed;
|
||||
bottom: clamp(14px, 3vw, 26px);
|
||||
left: var(--score-panel-left);
|
||||
z-index: 4;
|
||||
width: min(370px, calc(100vw - 32px));
|
||||
max-height: min(34vh, 292px);
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgb(238 185 73 / 0.3) transparent;
|
||||
border: 1px solid rgb(238 185 73 / 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
background: rgb(4 6 4 / 0.58);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transform: translateY(16px);
|
||||
transition:
|
||||
opacity 260ms ease,
|
||||
transform 260ms ease;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.kill-log::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.kill-log::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.kill-log::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
background: rgb(238 185 73 / 0.3);
|
||||
}
|
||||
|
||||
#app.match-live .kill-log.has-entries {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.kill-log-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.kill-log-item {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 54px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 54px;
|
||||
border: 1px solid rgb(255 244 209 / 0.12);
|
||||
border-radius: 6px;
|
||||
padding: 7px 9px;
|
||||
background: rgb(8 10 7 / 0.74);
|
||||
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.06);
|
||||
animation: kill-log-entry 180ms ease both;
|
||||
}
|
||||
|
||||
.kill-log-fighter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.kill-log-fighter.killer {
|
||||
border-left: 3px solid var(--killer-color);
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.kill-log-fighter.victim {
|
||||
flex-direction: row-reverse;
|
||||
border-right: 3px solid var(--victim-color);
|
||||
padding-right: 6px;
|
||||
justify-content: end;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.kill-log-avatar {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: 1px solid rgb(255 244 209 / 0.16);
|
||||
border-radius: 6px;
|
||||
background-color: rgb(255 246 216 / 0.08);
|
||||
background-position: -24px -16px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: auto 86px;
|
||||
image-rendering: pixelated;
|
||||
box-shadow: inset 0 -10px 18px rgb(0 0 0 / 0.22);
|
||||
}
|
||||
|
||||
.kill-log-fighter.victim .kill-log-avatar::before,
|
||||
.kill-log-fighter.victim .kill-log-avatar::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 1px;
|
||||
width: 14px;
|
||||
height: 2px;
|
||||
border: 1px solid rgb(255 216 212 / 0.22);
|
||||
border-radius: 999px;
|
||||
background: #f24a42;
|
||||
box-shadow:
|
||||
0 0 0 1px rgb(48 4 3 / 0.7),
|
||||
0 0 5px rgb(227 54 46 / 0.6);
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.kill-log-fighter.victim .kill-log-avatar::before {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.kill-log-fighter.victim .kill-log-avatar::after {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.kill-log-copy {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.kill-log-team,
|
||||
.kill-log-member {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.kill-log-team {
|
||||
min-width: 0;
|
||||
color: #fff7df;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 900;
|
||||
text-shadow: 1px 1px 2px #000;
|
||||
}
|
||||
|
||||
.kill-log-member {
|
||||
flex: 0 0 auto;
|
||||
color: #ead8ad;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.kill-log-action {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.kill-log-action-text {
|
||||
color: #ffdc93;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 950;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.kill-log-weapon {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
place-self: center;
|
||||
border: 1px solid rgb(238 185 73 / 0.28);
|
||||
border-radius: 999px;
|
||||
background: rgb(255 246 216 / 0.08);
|
||||
box-shadow: 0 0 16px rgb(227 89 59 / 0.16);
|
||||
}
|
||||
|
||||
.kill-log-weapon::before,
|
||||
.kill-log-weapon::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 18px;
|
||||
height: 3px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, #ffe8b4 0 70%, #b93c2f 70% 100%);
|
||||
box-shadow: 0 0 8px rgb(255 226 166 / 0.3);
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.kill-log-weapon::before {
|
||||
transform: translate(-50%, -50%) rotate(42deg);
|
||||
}
|
||||
|
||||
.kill-log-weapon::after {
|
||||
transform: translate(-50%, -50%) rotate(-42deg);
|
||||
}
|
||||
|
||||
.victory-celebration {
|
||||
position: fixed;
|
||||
z-index: 9;
|
||||
display: grid;
|
||||
overflow: hidden;
|
||||
place-items: center;
|
||||
inset: 0;
|
||||
background: rgb(4 6 4 / 0.2);
|
||||
isolation: isolate;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transform: scale(1);
|
||||
transition:
|
||||
opacity 220ms ease,
|
||||
transform 220ms ease;
|
||||
}
|
||||
|
||||
.victory-celebration.is-leaving {
|
||||
opacity: 0;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.victory-celebration::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
width: min(122vmin, 1240px);
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
background:
|
||||
radial-gradient(circle, rgb(255 233 166 / 0.18) 0 18%, rgb(227 178 79 / 0.12) 31%, transparent 66%);
|
||||
animation: victory-glow 1.8s ease-out both;
|
||||
}
|
||||
|
||||
.victory-celebration.is-draw::before {
|
||||
background:
|
||||
radial-gradient(circle, rgb(255 247 223 / 0.16) 0 18%, rgb(227 178 79 / 0.1) 31%, transparent 62%);
|
||||
}
|
||||
|
||||
.victory-rays {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
width: min(112vmin, 1120px);
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
background: repeating-conic-gradient(
|
||||
from -4deg,
|
||||
rgb(255 233 166 / 0.18) 0 8deg,
|
||||
transparent 8deg 18deg
|
||||
);
|
||||
opacity: 0.54;
|
||||
mask-image: radial-gradient(circle, #000 0 18%, transparent 66%);
|
||||
animation: victory-rays-in 1.1s ease-out both, victory-rays-turn 11s linear infinite;
|
||||
}
|
||||
|
||||
.victory-celebration.is-draw .victory-rays {
|
||||
opacity: 0.22;
|
||||
}
|
||||
|
||||
.victory-confetti {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.victory-confetti-piece {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: block;
|
||||
width: clamp(6px, 0.8vw, 11px);
|
||||
height: clamp(10px, 1.2vw, 18px);
|
||||
border-radius: 8px;
|
||||
background: var(--confetti-color);
|
||||
box-shadow: 0 0 12px rgb(255 230 166 / 0.22);
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%) rotate(var(--confetti-tilt)) scale(0.3);
|
||||
animation: victory-confetti-burst var(--confetti-duration) cubic-bezier(0.15, 0.84, 0.35, 1) var(--confetti-delay) both;
|
||||
}
|
||||
|
||||
.victory-confetti-piece:nth-child(3n) {
|
||||
width: clamp(10px, 1vw, 15px);
|
||||
height: clamp(6px, 0.72vw, 10px);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.victory-banner {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: grid;
|
||||
width: min(calc(100vw - 36px), 760px);
|
||||
min-height: clamp(108px, 18vw, 170px);
|
||||
overflow: hidden;
|
||||
place-items: center;
|
||||
border: 2px solid #f1c45d;
|
||||
border-radius: 8px;
|
||||
padding: clamp(1.25rem, 3.8vw, 2rem) clamp(1.3rem, 5.4vw, 3.4rem);
|
||||
background:
|
||||
linear-gradient(135deg, rgb(18 21 13 / 0.98), rgb(3 5 4 / 0.92)),
|
||||
rgb(4 6 4 / 0.9);
|
||||
color: #fff7df;
|
||||
font-size: clamp(1.65rem, 5vw, 3rem);
|
||||
font-weight: 950;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.12;
|
||||
text-align: center;
|
||||
text-wrap: balance;
|
||||
text-shadow:
|
||||
0 2px 0 rgb(55 36 8 / 0.56),
|
||||
0 0 24px rgb(255 226 153 / 0.28);
|
||||
box-shadow:
|
||||
0 0 0 1px rgb(255 237 187 / 0.2) inset,
|
||||
0 0 42px rgb(227 178 79 / 0.44),
|
||||
0 24px 90px rgb(0 0 0 / 0.58);
|
||||
animation: banner-in 0.64s cubic-bezier(0.16, 0.9, 0.25, 1.2);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.victory-banner::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -40% auto -40% -36%;
|
||||
width: 28%;
|
||||
background: linear-gradient(90deg, transparent, rgb(255 248 223 / 0.6), transparent);
|
||||
transform: skewX(-18deg);
|
||||
animation: victory-banner-sheen 1s 0.28s ease-out both;
|
||||
}
|
||||
|
||||
.victory-banner::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 10px;
|
||||
border: 1px solid rgb(255 225 151 / 0.24);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.victory-banner-message {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
animation: victory-message-pulse 720ms 80ms ease-out both;
|
||||
}
|
||||
|
||||
.victory-celebration.is-draw .victory-banner {
|
||||
border-color: #d8c28d;
|
||||
box-shadow:
|
||||
0 0 0 1px rgb(255 237 187 / 0.14) inset,
|
||||
0 0 28px rgb(227 178 79 / 0.24),
|
||||
0 24px 90px rgb(0 0 0 / 0.52);
|
||||
}
|
||||
|
||||
#app.match-paused .arena-shell::after {
|
||||
content: "일시정지";
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: 6;
|
||||
border: 1px solid rgb(238 185 73 / 0.34);
|
||||
border-radius: 8px;
|
||||
padding: 14px 26px;
|
||||
background: rgb(5 7 5 / 0.76);
|
||||
color: #ffe8b4;
|
||||
font-size: clamp(1.3rem, 4vw, 2rem);
|
||||
font-weight: 950;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow: 0 18px 60px rgb(0 0 0 / 0.46);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.match-status {
|
||||
position: fixed;
|
||||
bottom: clamp(14px, 3vw, 26px);
|
||||
left: 50%;
|
||||
z-index: 4;
|
||||
width: min(980px, calc(100vw - 32px));
|
||||
min-height: 48px;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgb(238 185 73 / 0.28);
|
||||
border-radius: 8px;
|
||||
padding: 13px 0;
|
||||
background: rgb(8 10 7 / 0.74);
|
||||
color: #ffe2a6;
|
||||
font-weight: 900;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, calc(100% + 28px));
|
||||
transition:
|
||||
opacity 420ms ease,
|
||||
transform 420ms ease;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
#app.status-active:not(.match-live) .match-status {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
#app.match-live .match-status {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 961px) {
|
||||
#app.match-live .match-status {
|
||||
left: calc((100vw - var(--drawer-width)) / 2);
|
||||
width: min(760px, calc(100vw - var(--drawer-width) - 32px));
|
||||
}
|
||||
|
||||
#app.match-live.drawer-collapsed .match-status {
|
||||
left: 50%;
|
||||
width: min(980px, calc(100vw - 32px));
|
||||
}
|
||||
}
|
||||
|
||||
.status-track {
|
||||
display: flex;
|
||||
width: max-content;
|
||||
min-width: 200%;
|
||||
gap: 64px;
|
||||
animation: status-marquee 22s linear infinite;
|
||||
}
|
||||
|
||||
.status-track span {
|
||||
flex: 0 0 auto;
|
||||
min-width: calc(50vw - 32px);
|
||||
padding-left: 28px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
.battle-preview {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2;
|
||||
overflow: hidden;
|
||||
opacity: 0.84;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity 420ms ease,
|
||||
transform 700ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
|
||||
#app.match-live .battle-preview {
|
||||
opacity: 0;
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.preview-fighter {
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: auto 100px;
|
||||
image-rendering: pixelated;
|
||||
transform-origin: center;
|
||||
filter:
|
||||
drop-shadow(0 18px 22px rgb(0 0 0 / 0.68))
|
||||
saturate(1.14)
|
||||
brightness(1.12);
|
||||
animation:
|
||||
preview-attack var(--sprite-speed) steps(var(--sprite-steps)) infinite,
|
||||
preview-breathe 1800ms ease-in-out infinite;
|
||||
}
|
||||
|
||||
.preview-knight {
|
||||
--sprite-end: -600px;
|
||||
--sprite-scale: 5.2;
|
||||
--sprite-speed: 840ms;
|
||||
--sprite-steps: 6;
|
||||
left: 10vw;
|
||||
top: 48vh;
|
||||
background-image: url("/assets/characters/knight/Knight-Attack01.png");
|
||||
transform: scale(var(--sprite-scale));
|
||||
}
|
||||
|
||||
.preview-orc {
|
||||
--sprite-end: -500px;
|
||||
--sprite-scale: 5.35;
|
||||
--sprite-speed: 760ms;
|
||||
--sprite-steps: 5;
|
||||
right: 9vw;
|
||||
top: 46vh;
|
||||
background-image: url("/assets/characters/orc/Orc-Attack01.png");
|
||||
transform: scaleX(-1) scale(var(--sprite-scale));
|
||||
}
|
||||
|
||||
.preview-wizard {
|
||||
--sprite-end: -500px;
|
||||
--sprite-scale: 4.35;
|
||||
--sprite-speed: 980ms;
|
||||
--sprite-steps: 5;
|
||||
left: 56vw;
|
||||
top: 24vh;
|
||||
background-image: url("/assets/characters/wizard/Wizard-Attack01.png");
|
||||
opacity: 0.58;
|
||||
transform: scaleX(-1) scale(var(--sprite-scale));
|
||||
}
|
||||
|
||||
.preview-strike {
|
||||
position: absolute;
|
||||
width: 160px;
|
||||
height: 5px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, transparent, rgb(255 229 156 / 0.86), transparent);
|
||||
box-shadow: 0 0 24px rgb(227 89 59 / 0.5);
|
||||
opacity: 0;
|
||||
transform-origin: center;
|
||||
animation: preview-strike 980ms ease-in-out infinite;
|
||||
}
|
||||
|
||||
.preview-strike-a {
|
||||
left: 38vw;
|
||||
top: 54vh;
|
||||
transform: rotate(-18deg);
|
||||
}
|
||||
|
||||
.preview-strike-b {
|
||||
right: 31vw;
|
||||
top: 42vh;
|
||||
transform: rotate(22deg);
|
||||
animation-delay: 260ms;
|
||||
}
|
||||
|
||||
.intro-stage {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 5;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: clamp(24px, 5vw, 56px);
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity 420ms ease,
|
||||
transform 620ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
|
||||
#app.match-live .intro-stage {
|
||||
opacity: 0;
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
#app.match-live .intro-content {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.intro-content {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: 22px;
|
||||
text-align: center;
|
||||
pointer-events: auto;
|
||||
animation: intro-rise 760ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
|
||||
transition:
|
||||
transform 560ms cubic-bezier(0.2, 0.8, 0.2, 1),
|
||||
opacity 360ms ease;
|
||||
}
|
||||
|
||||
#app.options-open:not(.match-live) .intro-content {
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.arena-logo {
|
||||
margin: 0;
|
||||
color: #fff4d1;
|
||||
font-size: clamp(4rem, 16vw, 11rem);
|
||||
font-weight: 950;
|
||||
letter-spacing: 0;
|
||||
line-height: 0.9;
|
||||
text-shadow:
|
||||
0 2px 0 #ad4d37,
|
||||
0 14px 42px rgb(0 0 0 / 0.72),
|
||||
0 0 40px rgb(230 173 71 / 0.28);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.arena-logo .small-text {
|
||||
font-size: 0.7em;
|
||||
margin-top: 0.05em;
|
||||
}
|
||||
|
||||
.arena-logo span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.arena-meta {
|
||||
position: fixed;
|
||||
right: clamp(10px, 2vw, 18px);
|
||||
bottom: clamp(10px, 2vw, 18px);
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
pointer-events: none;
|
||||
transition: opacity 220ms ease;
|
||||
}
|
||||
|
||||
.visitor-count,
|
||||
.about-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 28px;
|
||||
margin: 0;
|
||||
border: 1px solid rgb(238 185 73 / 0.22);
|
||||
border-radius: 999px;
|
||||
padding: 5px 12px;
|
||||
background: rgb(8 10 7 / 0.68);
|
||||
color: #e7c879;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
text-decoration: none;
|
||||
backdrop-filter: blur(10px);
|
||||
pointer-events: auto;
|
||||
transition:
|
||||
background 180ms ease,
|
||||
border-color 180ms ease,
|
||||
transform 180ms ease,
|
||||
opacity 220ms ease;
|
||||
}
|
||||
|
||||
.visitor-count {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
|
||||
#app.match-live .visitor-count {
|
||||
opacity: 0.86;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.about-button {
|
||||
min-width: 72px;
|
||||
color: #ffe8b4;
|
||||
font-weight: 900;
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 0.22);
|
||||
}
|
||||
|
||||
.about-button:hover {
|
||||
border-color: rgb(238 185 73 / 0.42);
|
||||
background: rgb(255 246 216 / 0.14);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.start-button {
|
||||
min-width: 180px;
|
||||
padding: 0 30px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#app.options-open:not(.match-live) .start-button {
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
|
@ -0,0 +1,308 @@
|
|||
@media (max-width: 960px) {
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app {
|
||||
--arena-gap: 0px;
|
||||
--mobile-game-size: min(100vw, calc(100svh - var(--score-band-height)));
|
||||
--mobile-kill-log-top: calc(var(--score-band-height) + var(--mobile-game-size) + 10px);
|
||||
--mobile-options-button-width: 54px;
|
||||
--mobile-options-gap: 8px;
|
||||
--mobile-team-card-width: clamp(56px, calc((100vw - 120px) / 4), 72px);
|
||||
--mobile-visitor-space: calc(104px + env(safe-area-inset-bottom));
|
||||
--score-band-height: 132px;
|
||||
--score-panel-left: 10px;
|
||||
--score-panel-width: calc(100vw - 20px);
|
||||
--score-rail-width: 0px;
|
||||
}
|
||||
|
||||
#app.match-live .arena-shell {
|
||||
place-items: start center;
|
||||
}
|
||||
|
||||
#app.match-live #game {
|
||||
width: var(--mobile-game-size);
|
||||
height: var(--mobile-game-size);
|
||||
margin-top: var(--score-band-height);
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.intro-stage {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.arena-logo {
|
||||
font-size: clamp(3.8rem, 22vw, 7rem);
|
||||
}
|
||||
|
||||
.fighter-entry {
|
||||
width: 100vw;
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
#app.match-live .fighter-entry {
|
||||
top: calc(10px + env(safe-area-inset-top));
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
width: auto;
|
||||
max-height: calc(100svh - 20px - env(safe-area-inset-top) - env(safe-area-inset-bottom));
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
#app.match-live.drawer-collapsed .fighter-entry {
|
||||
top: calc(22px + env(safe-area-inset-top));
|
||||
right: 10px;
|
||||
left: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#app.match-live .fighter-entry h2 {
|
||||
font-size: clamp(1.45rem, 7vw, 1.8rem);
|
||||
}
|
||||
|
||||
#app.match-live .fighter-entry textarea {
|
||||
height: 112px;
|
||||
min-height: 112px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
#app.match-live .fighter-entry fieldset {
|
||||
gap: 7px;
|
||||
padding: 9px;
|
||||
}
|
||||
|
||||
#app.match-live .fighter-entry form {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#app.match-live .entry-copy {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
#app.match-live .eyebrow {
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
|
||||
#app.match-live label,
|
||||
#app.match-live .spawn-placement-label {
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
#app.match-live input:not([type="range"]):not([type="radio"]),
|
||||
#app.match-live textarea {
|
||||
min-height: 40px;
|
||||
padding-inline: 10px;
|
||||
}
|
||||
|
||||
#app.match-live textarea {
|
||||
padding-block: 9px;
|
||||
}
|
||||
|
||||
#app.match-live .team-size-number {
|
||||
width: 64px;
|
||||
min-width: 64px;
|
||||
}
|
||||
|
||||
#app.match-live .spawn-placement-option span {
|
||||
min-height: 36px;
|
||||
padding: 6px;
|
||||
font-size: 0.76rem;
|
||||
}
|
||||
|
||||
#app.match-live .match-actions {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#app.match-live .match-actions button {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
#app.match-live .drawer-toggle {
|
||||
min-width: 116px;
|
||||
}
|
||||
|
||||
#app.match-live.drawer-collapsed .drawer-toggle {
|
||||
width: var(--mobile-options-button-width);
|
||||
min-width: var(--mobile-options-button-width);
|
||||
padding-inline: 6px;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
#app.match-live.drawer-collapsed .drawer-toggle::before {
|
||||
content: "옵션";
|
||||
font-size: 0.78rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.battle-preview {
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.preview-knight {
|
||||
left: -8vw;
|
||||
top: 52vh;
|
||||
}
|
||||
|
||||
.preview-orc {
|
||||
right: -9vw;
|
||||
top: 49vh;
|
||||
}
|
||||
|
||||
.preview-wizard {
|
||||
left: 48vw;
|
||||
top: 21vh;
|
||||
}
|
||||
|
||||
.scoreboard {
|
||||
align-items: flex-start;
|
||||
top: 10px;
|
||||
left: var(--score-panel-left);
|
||||
width: var(--score-panel-width);
|
||||
max-height: calc(var(--score-band-height) - 12px);
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding: 9px min(148px, 38vw) 9px 9px;
|
||||
scrollbar-color: rgb(238 185 73 / 0.38) transparent;
|
||||
scrollbar-width: thin;
|
||||
touch-action: pan-x;
|
||||
}
|
||||
|
||||
#app.match-live.drawer-collapsed .scoreboard {
|
||||
width: calc(
|
||||
100vw - 20px - var(--mobile-options-button-width) - var(--mobile-options-gap)
|
||||
);
|
||||
padding-right: 9px;
|
||||
}
|
||||
|
||||
.score-side {
|
||||
display: grid;
|
||||
grid-template-columns: none;
|
||||
grid-auto-columns: var(--mobile-team-card-width);
|
||||
grid-auto-flow: column;
|
||||
grid-template-rows: repeat(2, 48px);
|
||||
gap: 5px 5px;
|
||||
}
|
||||
|
||||
.scoreboard::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.scoreboard::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.scoreboard::-webkit-scrollbar-thumb {
|
||||
border-radius: 999px;
|
||||
background: rgb(238 185 73 / 0.38);
|
||||
}
|
||||
|
||||
.team-score {
|
||||
width: auto;
|
||||
min-height: 48px;
|
||||
height: 48px;
|
||||
gap: 3px;
|
||||
padding: 5px 6px;
|
||||
font-size: 0.66rem;
|
||||
grid-template-rows: 1fr 1px auto;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.team-score-count {
|
||||
font-size: 0.74rem;
|
||||
}
|
||||
|
||||
.team-score.is-focused {
|
||||
box-shadow: inset 0 0 0 2px rgb(255 244 209 / 0.92);
|
||||
}
|
||||
|
||||
.battle-notice {
|
||||
top: calc(var(--score-band-height) + 8px);
|
||||
right: 24px;
|
||||
left: 24px;
|
||||
width: auto;
|
||||
padding-inline: 12px;
|
||||
font-size: 0.76rem;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
|
||||
#app.match-live .battle-notice.is-visible {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.kill-log {
|
||||
top: var(--mobile-kill-log-top);
|
||||
bottom: auto;
|
||||
left: 10px;
|
||||
width: calc(100vw - 20px);
|
||||
max-height: calc(100svh - var(--mobile-kill-log-top) - var(--mobile-visitor-space));
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#app.match-live .victory-celebration {
|
||||
padding:
|
||||
var(--score-band-height)
|
||||
14px
|
||||
min(30svh, 230px);
|
||||
}
|
||||
|
||||
.victory-banner {
|
||||
width: min(calc(100vw - 48px), 520px);
|
||||
min-height: 92px;
|
||||
padding: 1rem 1.1rem;
|
||||
font-size: clamp(1.35rem, 7vw, 2rem);
|
||||
}
|
||||
|
||||
.match-status {
|
||||
bottom: 10px;
|
||||
width: calc(100vw - 20px);
|
||||
}
|
||||
|
||||
.arena-meta {
|
||||
right: 10px;
|
||||
bottom: calc(10px + env(safe-area-inset-bottom));
|
||||
z-index: 10;
|
||||
gap: 8px;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.visitor-count {
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
|
||||
.about-button {
|
||||
min-width: 68px;
|
||||
min-height: 26px;
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
|
||||
.about-backdrop {
|
||||
align-items: end;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.about-dialog {
|
||||
width: 100%;
|
||||
max-height: calc(100svh - 24px);
|
||||
}
|
||||
|
||||
.about-header {
|
||||
padding: 18px 18px 12px;
|
||||
}
|
||||
|
||||
.about-tabs {
|
||||
padding: 0 18px 12px;
|
||||
}
|
||||
|
||||
.about-panel {
|
||||
padding: 16px 18px 22px;
|
||||
}
|
||||
|
||||
.about-field-row {
|
||||
grid-template-columns: 78px minmax(0, 1fr);
|
||||
min-height: 48px;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,566 @@
|
|||
.start-button,
|
||||
form button[type="submit"],
|
||||
.pause-button,
|
||||
.restart-button {
|
||||
min-height: 52px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(180deg, #e56443, #b93c2f);
|
||||
color: #fff7df;
|
||||
font-weight: 900;
|
||||
box-shadow:
|
||||
0 18px 44px rgb(0 0 0 / 0.36),
|
||||
inset 0 1px 0 rgb(255 255 255 / 0.2);
|
||||
transition:
|
||||
background 180ms ease,
|
||||
transform 180ms ease,
|
||||
box-shadow 180ms ease;
|
||||
}
|
||||
|
||||
.start-button:hover,
|
||||
form button[type="submit"]:hover,
|
||||
.pause-button:hover,
|
||||
.restart-button:hover {
|
||||
background: linear-gradient(180deg, #f0754f, #c84636);
|
||||
transform: translateY(-1px);
|
||||
box-shadow:
|
||||
0 22px 52px rgb(0 0 0 / 0.42),
|
||||
inset 0 1px 0 rgb(255 255 255 / 0.24);
|
||||
}
|
||||
|
||||
.pause-button,
|
||||
.restart-button {
|
||||
display: none;
|
||||
border: 1px solid rgb(238 185 73 / 0.3);
|
||||
background: rgb(255 246 216 / 0.08);
|
||||
color: #ffe8b4;
|
||||
}
|
||||
|
||||
.pause-button:hover,
|
||||
.restart-button:hover {
|
||||
background: rgb(255 246 216 / 0.14);
|
||||
}
|
||||
|
||||
#app.match-live .pause-button,
|
||||
#app.match-live .restart-button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#app.match-live .match-actions {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
#app.match-live .match-actions button[type="submit"] {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
#app.match-paused .pause-button {
|
||||
background: linear-gradient(180deg, #e3b24f, #9a6c24);
|
||||
color: #120f08;
|
||||
}
|
||||
|
||||
#app.match-ended .pause-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.drawer-scrim {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 6;
|
||||
background: rgb(4 5 4 / 0.42);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 320ms ease;
|
||||
}
|
||||
|
||||
#app.options-open .drawer-scrim {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#app.match-live .drawer-scrim {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.drawer-toggle {
|
||||
display: none;
|
||||
min-height: 40px;
|
||||
border: 1px solid rgb(238 185 73 / 0.28);
|
||||
border-radius: 8px;
|
||||
padding: 0 12px;
|
||||
background: rgb(12 15 11 / 0.84);
|
||||
color: #ffe8b4;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 900;
|
||||
box-shadow: 0 16px 38px rgb(0 0 0 / 0.36);
|
||||
transition:
|
||||
background 180ms ease,
|
||||
transform 180ms ease;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.drawer-toggle:hover {
|
||||
background: rgb(255 246 216 / 0.14);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
#app.match-live .drawer-toggle {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.fighter-entry {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 7;
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: 24px;
|
||||
width: var(--drawer-width);
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
border-left: 1px solid rgb(239 199 103 / 0.22);
|
||||
padding: clamp(22px, 4vw, 34px);
|
||||
background:
|
||||
linear-gradient(180deg, rgb(29 33 22 / 0.94), rgb(13 16 12 / 0.96)),
|
||||
#11140f;
|
||||
box-shadow: -28px 0 80px rgb(0 0 0 / 0.52);
|
||||
transform: translateX(104%);
|
||||
transition:
|
||||
opacity 260ms ease,
|
||||
transform 520ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
|
||||
#app.options-open .fighter-entry {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
#app.match-live .fighter-entry {
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
height: auto;
|
||||
max-height: calc(100vh - 48px);
|
||||
gap: 16px;
|
||||
border: 1px solid rgb(239 199 103 / 0.22);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
#app.match-live.drawer-collapsed .fighter-entry {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
overflow: visible;
|
||||
border-color: transparent;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
#app.match-live .drawer-close {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#app.match-live .fighter-entry h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
#app.match-live .fighter-entry textarea {
|
||||
min-height: 190px;
|
||||
}
|
||||
|
||||
#app.match-live .fighter-entry fieldset {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
#app.match-live.drawer-collapsed .entry-copy,
|
||||
#app.match-live.drawer-collapsed .fighter-entry form {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#app.match-live.drawer-collapsed .drawer-header {
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.drawer-header {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.drawer-header-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.entry-copy {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0;
|
||||
color: #e3b24f;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: #fff3d2;
|
||||
font-size: clamp(1.7rem, 4vw, 2.5rem);
|
||||
line-height: 1.05;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.drawer-close {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex: 0 0 auto;
|
||||
border: 1px solid rgb(238 185 73 / 0.22);
|
||||
border-radius: 8px;
|
||||
background: rgb(255 246 216 / 0.08);
|
||||
color: #f8deb0;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.drawer-close:hover {
|
||||
background: rgb(255 246 216 / 0.14);
|
||||
}
|
||||
|
||||
.about-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 20;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: clamp(16px, 4vw, 34px);
|
||||
background: rgb(3 5 4 / 0.66);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.about-backdrop[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.about-dialog {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto minmax(0, 1fr);
|
||||
width: min(560px, calc(100vw - 32px));
|
||||
max-height: min(760px, calc(100svh - 32px));
|
||||
overflow: hidden;
|
||||
border: 1px solid rgb(239 199 103 / 0.28);
|
||||
border-radius: 8px;
|
||||
background:
|
||||
linear-gradient(180deg, rgb(29 33 22 / 0.98), rgb(10 13 9 / 0.98)),
|
||||
#11140f;
|
||||
box-shadow:
|
||||
0 24px 100px rgb(0 0 0 / 0.62),
|
||||
inset 0 1px 0 rgb(255 255 255 / 0.06);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.about-header {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: clamp(20px, 4vw, 28px) clamp(20px, 4vw, 30px) 14px;
|
||||
}
|
||||
|
||||
.about-close {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex: 0 0 auto;
|
||||
border: 1px solid rgb(238 185 73 / 0.22);
|
||||
border-radius: 8px;
|
||||
background: rgb(255 246 216 / 0.08);
|
||||
color: #f8deb0;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.about-close:hover {
|
||||
background: rgb(255 246 216 / 0.14);
|
||||
}
|
||||
|
||||
.about-tabs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 4px;
|
||||
padding: 0 clamp(20px, 4vw, 30px) 14px;
|
||||
border-bottom: 1px solid rgb(238 185 73 / 0.16);
|
||||
}
|
||||
|
||||
.about-tab {
|
||||
min-width: 0;
|
||||
min-height: 42px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
padding: 8px 10px;
|
||||
background: rgb(255 246 216 / 0.06);
|
||||
color: #ead8ad;
|
||||
font-size: 0.86rem;
|
||||
font-weight: 900;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.about-tab[aria-selected="true"] {
|
||||
border-color: rgb(238 185 73 / 0.36);
|
||||
background: #323822;
|
||||
color: #fff7df;
|
||||
}
|
||||
|
||||
.about-panel {
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: clamp(18px, 4vw, 26px) clamp(20px, 4vw, 30px) clamp(22px, 5vw, 34px);
|
||||
}
|
||||
|
||||
.about-fields {
|
||||
display: grid;
|
||||
gap: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.about-field-row {
|
||||
display: grid;
|
||||
grid-template-columns: 96px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
min-height: 50px;
|
||||
border-bottom: 1px solid rgb(238 185 73 / 0.14);
|
||||
}
|
||||
|
||||
.about-field-row:first-child {
|
||||
border-top: 1px solid rgb(238 185 73 / 0.14);
|
||||
}
|
||||
|
||||
.about-field-row dt {
|
||||
color: #e3b24f;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 950;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.about-field-row dd {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
color: #fff7df;
|
||||
font-weight: 800;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.about-field-row a,
|
||||
.about-markdown a {
|
||||
color: #85dcc7;
|
||||
text-decoration-color: rgb(133 220 199 / 0.42);
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.about-markdown {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
color: #ead8ad;
|
||||
font-size: 0.92rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.56;
|
||||
}
|
||||
|
||||
.about-markdown :is(h3, h4, h5, h6, p, ul, blockquote) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.about-markdown h3,
|
||||
.about-markdown h4,
|
||||
.about-markdown h5,
|
||||
.about-markdown h6 {
|
||||
color: #fff3d2;
|
||||
font-size: 1rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.about-markdown blockquote {
|
||||
border-left: 3px solid rgb(238 185 73 / 0.36);
|
||||
padding: 4px 0 4px 16px;
|
||||
color: #c4b693;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.about-markdown hr {
|
||||
margin: 8px 0;
|
||||
border: 0;
|
||||
border-top: 1px solid rgb(238 185 73 / 0.16);
|
||||
}
|
||||
|
||||
.about-markdown code {
|
||||
border: 1px solid rgb(238 185 73 / 0.14);
|
||||
border-radius: 4px;
|
||||
padding: 2px 5px;
|
||||
background: rgb(255 246 216 / 0.08);
|
||||
color: #f1c761;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
|
||||
.about-markdown li {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.about-markdown li strong {
|
||||
color: #fff3d2;
|
||||
}
|
||||
|
||||
.about-markdown ul {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
padding-left: 1.2rem;
|
||||
}
|
||||
|
||||
.about-empty {
|
||||
color: #bfae83;
|
||||
}
|
||||
|
||||
form {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.match-actions {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
border: 1px solid rgb(238 185 73 / 0.22);
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
background: rgb(5 7 5 / 0.26);
|
||||
}
|
||||
|
||||
legend {
|
||||
padding: 0 6px;
|
||||
color: #e3b24f;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.team-size-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.team-size-number {
|
||||
width: 88px;
|
||||
min-width: 88px;
|
||||
padding-inline: 10px;
|
||||
text-align: center;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
label {
|
||||
color: #ead8ad;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
input:not([type="range"]):not([type="radio"]),
|
||||
textarea {
|
||||
min-height: 48px;
|
||||
border: 1px solid rgb(238 185 73 / 0.28);
|
||||
border-radius: 8px;
|
||||
padding: 0 14px;
|
||||
background: #232719;
|
||||
color: #fff7df;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 258px;
|
||||
resize: vertical;
|
||||
padding-block: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
width: 100%;
|
||||
accent-color: #e3b24f;
|
||||
}
|
||||
|
||||
.spawn-placement-field {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.spawn-placement-label {
|
||||
color: #ead8ad;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.spawn-placement-options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
border: 1px solid rgb(238 185 73 / 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
background: #1d2116;
|
||||
}
|
||||
|
||||
.spawn-placement-option {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.spawn-placement-option input {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.spawn-placement-option span {
|
||||
display: grid;
|
||||
min-height: 44px;
|
||||
place-items: center;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
color: #ead8ad;
|
||||
text-align: center;
|
||||
font-size: 0.86rem;
|
||||
font-weight: 900;
|
||||
line-height: 1.25;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.spawn-placement-option input:checked + span {
|
||||
border-color: rgb(238 185 73 / 0.36);
|
||||
background: #323822;
|
||||
color: #fff7df;
|
||||
}
|
||||
|
||||
.spawn-placement-option input:focus-visible + span {
|
||||
outline: 2px solid #f1c761;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
86
todo.md
86
todo.md
|
|
@ -109,12 +109,12 @@
|
|||
18. 치명타 적중 표기 추가 (완료)
|
||||
- **조치 사항**:
|
||||
- 공격 프로필의 치명타 판정을 실제 적중 처리까지 전달해 전투 타입별 적중 연출이 같은 흐름을 사용하도록 정리.
|
||||
- 치명타 적중 시 대상 위에 `Critical!` 문구를 띄우고 즉시 처치와 카메라 흔들림이 함께 적용되도록 `applyHit()`를 보강.
|
||||
- 치명타 적중 시 대상 위에 `Critical!` 문구를 띄우고 즉시 처치가 적용되도록 `applyHit()`를 보강. (카메라 흔들림은 이후 메테오 착탄 연출로 이전)
|
||||
|
||||
19. 리스폰 배치 설정 구분 추가 (완료)
|
||||
- **조치 사항**:
|
||||
- 전투 설정 drawer에 `스타팅 지점 배치`와 기존 `완전 랜덤 배치`를 선택하는 리스폰 설정을 추가.
|
||||
- `스타팅 지점 배치`에서는 참가자 수에 맞춰 전장 구역을 나누고 참가자별 시작 구역 배정과 구역 안 스폰 위치를 매치마다 무작위로 정하도록 구현.
|
||||
- `스타팅 지점 배치`에서는 참가자별 스타팅 영역과 영역 안 스폰 위치를 매치마다 무작위로 정하도록 구현했으며, 이후 30번 작업에서 영역 선택을 랜덤 중심 셀 기반 `5 x 5` 방식으로 구체화.
|
||||
- 선택한 리스폰 배치 모드를 `localStorage`에 저장해 새로고침과 재시작 이후에도 유지.
|
||||
|
||||
20. 팀당 인원 직접 입력 동기화 (완료)
|
||||
|
|
@ -178,4 +178,86 @@
|
|||
- 유저가 About 다이얼로그를 열 때마다 DB에서 최신 데이터를 가져오도록 서버 메모리 캐시 로직을 제거.
|
||||
- 기본 개인정보처리방침 마크다운의 공고/시행 일자를 최신화.
|
||||
|
||||
28. 전투 역할별 기본 스탯 프로필 분리 (완료)
|
||||
- **조치 사항**:
|
||||
- `src/constants.js`에 `FIGHTER_TYPE_STATS.melee/ranged/magic` 프로필을 추가해 최대 체력, 이동속도, 사거리, 쿨다운, 피해량, 치명타, 공격 발동 지연을 역할별로 조정할 수 있도록 변경.
|
||||
- `src/game/fighter/fighterStats.js`를 추가해 투사체 캐릭터는 원거리, 즉발 주문 캐릭터는 마법, 나머지는 근접 프로필로 판별하고 개별 스킨 오버라이드를 병합.
|
||||
- 캐릭터 생성과 전투 엔진이 해석된 프로필을 사용하도록 연결해 역할별 체력, 이동 및 공격 수치가 실제 전투에 적용되도록 변경.
|
||||
|
||||
29. 킬로그 캐릭터 아이콘 가시성 개선 (완료)
|
||||
- **조치 사항**:
|
||||
- `100x100` idle 프레임에 포함된 투명 여백까지 축소되던 킬로그 아이콘 배경 표시 방식을 보정.
|
||||
- 아이콘 박스 크기와 행 레이아웃은 유지하면서 캐릭터 실루엣이 있는 중앙 하단 영역을 확대 표시하도록 배경 크기와 위치를 조정.
|
||||
|
||||
30. 팀별 스타팅 영역 앵커 및 전장 표시 추가 (완료)
|
||||
- **조치 사항**:
|
||||
- `스타팅 지점 배치`에서 전장 스폰 가능 그리드 중 팀별 중심 셀을 무작위로 선택하고, 중심 주변 2칸을 포함하는 `5 x 5` 영역을 팀별 스폰 구역으로 사용하도록 변경.
|
||||
- 겹치지 않는 후보가 남아 있는 동안에는 선택된 스타팅 영역끼리 중첩되지 않는 랜덤 중심을 우선 사용해 전투 시작 즉시 팀이 섞이는 상황을 줄임.
|
||||
- 팀별로 무작위 배정된 스타팅 영역 데이터를 실제 스폰 좌표와 공유해 표시 영역 밖에서 시작하지 않도록 구성.
|
||||
- `arenaRenderer.js`에 팀 색상의 매우 옅은 채움 및 외곽선 오버레이를 추가하고, 랜덤 배치에서는 오버레이가 표시되지 않도록 연결.
|
||||
|
||||
31. 사망 시점 팀 badge 클릭 입력 유실 수정 (완료)
|
||||
- **조치 사항**:
|
||||
- 사망 발생 때마다 `arenaScoreboard.js`가 팀 badge 버튼 전체를 재생성해 클릭 중인 DOM이 제거되던 문제를 수정.
|
||||
- 팀 구성이 바뀌지 않는 전투 중 갱신에서는 기존 버튼 DOM을 유지하고 생존 인원, 선택 강조, 비활성 상태만 업데이트하도록 변경.
|
||||
- 사망 처리와 팀 badge 클릭이 같은 시점에 겹쳐도 생존 캐릭터 관전 시점 선택이 정상 전달되도록 보강.
|
||||
|
||||
32. 주기적 월드 이펙트 메테오 및 냉각지대 추가 (완료)
|
||||
- **조치 사항**:
|
||||
- `public/assets/effects/world_Effect.png`를 7프레임 공용 스프라이트시트로 로드하고, 실제 전투 시작 후 8초마다 무작위 생존자 위치에 메테오 또는 냉각지대를 무작위 발동하도록 `worldEffects.js`를 추가.
|
||||
- 메테오는 낙하 경고 후 대상 위치 기준 `5 x 5` 영역에 환경 피해를 적용하고, 환경 사망이 처치 보상 없이 사망 통계와 승패 판정에 반영되도록 전투 피해 처리를 확장.
|
||||
- 냉각지대는 냉기 착탄 연출과 지속 구역을 표시하며, 구역 안에 있는 캐릭터의 공격속도와 이동속도를 함께 감속하도록 연결.
|
||||
- 발동 간격, 범위, 피해량, 냉각 지속시간과 감속 배율을 `src/constants.js`의 `WORLD_EFFECT_*` 상수로 분리하고, 새 경기/종료/일시정지 생명주기에 맞춰 정리되도록 구성.
|
||||
|
||||
33. 월드 메테오 대각선 낙하 및 냉기 전용 시트 적용 (완료)
|
||||
- **조치 사항**:
|
||||
- 대상 위치가 전장 좌측 반면(2, 3사분면)이면 좌상단에서 우하단, 우측 반면(1, 4사분면)이면 우상단에서 좌하단으로 낙하하도록 궤적, 좌우 반전, `45`도 회전을 적용.
|
||||
- `WORLD_EFFECT_VISUAL_SCALE`과 `WORLD_EFFECT_FALL_TRAVEL_TILES`를 추가해 피해 판정 `5 x 5`는 유지하면서 스프라이트를 전역 마법처럼 크게 보이도록 확장.
|
||||
- 화염 메테오는 `public/assets/effects/world_Effect.png`, 냉기 메테오는 새 `public/assets/effects/world_Effect_2.png`를 각각 독립된 7프레임 애니메이션으로 로드하도록 변경.
|
||||
|
||||
34. 냉기 메테오 착탄 피해 옵션 추가 (완료)
|
||||
- **조치 사항**:
|
||||
- `WORLD_EFFECT_FROST_DAMAGE`를 추가해 냉기 메테오 피해를 화염 메테오와 독립적으로 조절할 수 있도록 변경.
|
||||
- 냉기 메테오 착탄 시 `5 x 5` 영역 피해를 먼저 처리하고, 전투가 종료되지 않은 경우 기존 냉각지대 감속 효과를 이어서 생성하도록 연결.
|
||||
|
||||
35. 스타팅 영역 표시 시간 제한 추가 (완료)
|
||||
- **조치 사항**:
|
||||
- 팀별 스타팅 영역 오버레이가 `스타팅 지점 배치` 매치 시작 후 5초 동안만 표시되고 이후 자동으로 사라지도록 연결.
|
||||
- 숨김 예약을 Phaser 씬 타이머로 관리하여 일시정지 시간은 표시 지속 시간에 포함되지 않고, 새 매치 시작 시 이전 숨김 타이머가 남지 않도록 정리.
|
||||
|
||||
36. 최종 2팀 자동 관전 및 메테오 착탄 화면 흔들림 전환 (완료)
|
||||
- **조치 사항**:
|
||||
- 생존 캐릭터가 30명 미만이거나 최종 2팀만 남으면 후반 자동 줌과 교전 중심 포커싱이 시작되도록 관전 조건을 확장.
|
||||
- 치명타의 `Critical!` 표기와 즉시 처치는 유지하면서 카메라 흔들림을 제거.
|
||||
- 화염 메테오가 착탄할 때 화면 흔들림을 적용하고, 냉각지대 착탄과 피해 계산에서는 흔들림을 분리.
|
||||
|
||||
37. 자동 관전 이전 메테오 임시 포커싱 추가 (완료)
|
||||
- **조치 사항**:
|
||||
- 후반 자동 관전 조건이 성립하기 전 화염 또는 냉기 메테오가 낙하하면 착탄 위치를 확대 추적하고 착탄 연출 종료 후 기존 카메라 위치와 줌을 복원.
|
||||
- 캐릭터 수동 선택과 후반/최종 자동 관전은 메테오 임시 시점보다 우선하도록 카메라 상태를 정리.
|
||||
- `src/constants.js`의 `CAMERA.METEOR_FOCUS_ENABLED` 플래그로 메테오 임시 포커싱을 코드에서 켜고 끌 수 있도록 구성.
|
||||
|
||||
38. 냉기 메테오 동결 기절 및 실루엣 효과 추가 (완료)
|
||||
- **조치 사항**:
|
||||
- 냉기 메테오 착탄 피해에 생존한 전투원은 `2초` 동안 이동과 새 공격이 정지되는 `isFrostStunned` 상태가 되도록 연결.
|
||||
- 동결 중 캐릭터 본체와 팀 실루엣 마커를 함께 얼음색으로 틴트하고, 시간이 끝나거나 매치가 정리되면 본체 원본 색상과 팀 색상으로 복원.
|
||||
- `WORLD_EFFECT.FROST_STUN_DURATION`과 `WORLD_EFFECT.FROST_STUN_TINT`를 추가해 동결 지속시간과 표시 색상을 조절 가능하게 구성.
|
||||
|
||||
39. 모바일 세로모드 팀 카드 가로폭 불균형 수정 (완료)
|
||||
- **조치 사항**:
|
||||
- 모바일 미디어 쿼리에서 `.score-side`가 데스크톱의 `grid-template-columns: repeat(2, 114px)`를 상속받아 1~4번 팀 카드만 길게 표시되던 현상을 수정.
|
||||
- `grid-template-columns: none`을 추가하여 모든 팀 카드가 `grid-auto-columns`에 설정된 일정한 가로폭을 가지도록 보정.
|
||||
|
||||
40. CSS 파일 기능별 모듈화 (완료)
|
||||
- **조치 사항**:
|
||||
- 거대했던 `src/styles.css`(약 2,000라인)를 기능별로 6개의 파일(`base`, `intro`, `game-ui`, `overlay`, `animations`, `mobile`)로 분리.
|
||||
- `src/styles/` 폴더를 생성하여 모듈화된 CSS 파일들을 관리.
|
||||
- `src/styles.css`는 이제 `@import`를 통해 각 모듈을 통합하는 엔트리 포인트 역할만 수행.
|
||||
- 코드 가독성과 유지보수 편의성을 대폭 향상.
|
||||
|
||||
41. 스타일 관련 컨텍스트 문서 추가 및 라우팅 업데이트 (완료)
|
||||
- **조치 사항**:
|
||||
- 새로운 CSS 모듈 구조와 디자인 원칙을 설명하는 `context/style.md` 문서를 신규 생성.
|
||||
- `agent.md`의 상세 기술 가이드(Context Routing) 섹션에 스타일 및 디자인 항목을 추가하여 문서 접근성 개선.
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue