Compare commits

...

5 Commits

5 changed files with 191 additions and 73 deletions

View File

@@ -1,5 +1,25 @@
# 업데이트 로그 # 업데이트 로그
## 2026-03-31 v1.2.67
- 홈 화면 게임 템플릿 즐겨찾기 버튼 위치 변경은 유지하면서, 즐겨찾기 on/off 시 카드가 즉시 튀지 않고 부드럽게 재정렬되도록 이동/페이드 전환을 추가함.
- 별 아이콘을 눌렀을 때 카드가 즐겨찾기 우선순위 위치로 자연스럽게 이동해 전체 라이브러리 전환감이 덜 거칠게 보이도록 보정함.
## 2026-03-31 v1.2.66
- 내 티어표 카드 하단의 큰 삭제 버튼은 제거하고, 삭제는 상세 편집 화면에서만 하도록 흐름을 단순화함.
- 내 티어표 카드 그리드를 고정 4/3/2열에서 `auto-fit` 기반 최소 폭 카드로 바꾸고, 제목/메타가 좁은 화면에서도 말줄임과 유연한 폭 계산을 유지하도록 보정함.
## 2026-03-31 v1.2.65
- 에디터 옵션 토글의 라벨과 스위치 순서를 바꾼 뒤 체크 상태 셀렉터가 끊긴 문제를 보정해, 왼쪽 라벨·오른쪽 스위치 배치에서도 정상 동작하도록 수정함.
- 왼쪽 사이드 축소 상태 검색은 전용 모달의 기본 스타일이 빠져 있던 문제를 복구해, 다시 중앙 상단 검색 오버레이로 열리도록 정리함.
## 2026-03-31 v1.2.64
- 메인 콘텐츠가 길어질 때 스크롤 끝이 화면 바닥에 붙지 않도록 중앙 워크스페이스 하단 여백을 추가하고, 긴 작업 화면에서도 마감선이 답답하지 않게 보정함.
- 템플릿 요청 모달 입력창을 Settings 화면과 같은 어두운 언더라인 입력 문법으로 통일하고, 에디터의 공개/이름 표시 옵션은 체크박스 대신 스위치형 토글로 재구성함.
## 2026-03-31 v1.2.63
- 앱 셸과 워크스페이스에 걸려 있던 고정 `100dvh` 높이를 풀어, 본문이 길어질 때 중앙 `main` 영역이 잘리거나 접히는 현상을 보정함.
- 좌우 레일은 그대로 화면 기준 높이를 유지하되, 중앙 작업 영역은 내용만큼 자연스럽게 늘어나도록 높이 계산을 다시 정리함.
## 2026-03-31 v1.2.62 ## 2026-03-31 v1.2.62
- 템플릿 요청 모달의 제목/설명 입력을 Settings 화면과 같은 어두운 입력 문법으로 맞춰 흰 배경/흰 글자처럼 보이던 문제를 정리함. - 템플릿 요청 모달의 제목/설명 입력을 Settings 화면과 같은 어두운 입력 문법으로 맞춰 흰 배경/흰 글자처럼 보이던 문제를 정리함.
- 앱 셸은 사이드 기본 바탕색을 중심으로 재정리하고, 중앙 바디에 배경과 좌우 보더를 줘 긴 스크롤에서도 사이드가 잘리는 듯한 인상을 줄이도록 조정함. - 앱 셸은 사이드 기본 바탕색을 중심으로 재정리하고, 중앙 바디에 배경과 좌우 보더를 줘 긴 스크롤에서도 사이드가 잘리는 듯한 인상을 줄이도록 조정함.

View File

@@ -416,7 +416,6 @@ function submitGlobalSearch() {
<style scoped> <style scoped>
.appShell { .appShell {
min-height: 100dvh; min-height: 100dvh;
height: 100dvh;
display: grid; display: grid;
grid-template-columns: var(--left-rail-width, 248px) minmax(0, 1fr) var(--right-rail-width, 320px); grid-template-columns: var(--left-rail-width, 248px) minmax(0, 1fr) var(--right-rail-width, 320px);
background: rgba(14, 14, 14, 0.96); background: rgba(14, 14, 14, 0.96);
@@ -431,7 +430,6 @@ function submitGlobalSearch() {
.leftRail, .leftRail,
.rightRail { .rightRail {
min-height: 100dvh; min-height: 100dvh;
height: 100dvh;
border-right: 1px solid rgba(255, 255, 255, 0.08); border-right: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(14, 14, 14, 0.92); background: rgba(14, 14, 14, 0.92);
box-sizing: border-box; box-sizing: border-box;
@@ -790,7 +788,7 @@ function submitGlobalSearch() {
.appMain { .appMain {
min-width: 0; min-width: 0;
min-height: 100dvh; min-height: 0;
box-sizing: border-box; box-sizing: border-box;
background: rgba(18, 18, 18, 0.98); background: rgba(18, 18, 18, 0.98);
border-left: 1px solid rgba(255, 255, 255, 0.08); border-left: 1px solid rgba(255, 255, 255, 0.08);
@@ -806,7 +804,6 @@ function submitGlobalSearch() {
grid-template-rows: 56px minmax(0, 1fr); grid-template-rows: 56px minmax(0, 1fr);
gap: 0; gap: 0;
min-height: 100dvh; min-height: 100dvh;
height: 100dvh;
} }
.workspace--localRail { .workspace--localRail {
@@ -848,7 +845,7 @@ function submitGlobalSearch() {
.workspaceBody { .workspaceBody {
min-height: 0; min-height: 0;
padding: 18px 18px 0; padding: 18px 18px 32px;
border: 0; border: 0;
border-radius: 0; border-radius: 0;
background: rgba(24, 24, 24, 0.92); background: rgba(24, 24, 24, 0.92);
@@ -858,7 +855,7 @@ function submitGlobalSearch() {
.workspaceBody--localRail { .workspaceBody--localRail {
min-height: 0; min-height: 0;
padding: 18px 18px 0; padding: 18px 18px 32px;
border: 0; border: 0;
border-radius: 0; border-radius: 0;
background: rgba(24, 24, 24, 0.92); background: rgba(24, 24, 24, 0.92);
@@ -896,6 +893,60 @@ function submitGlobalSearch() {
display: none; display: none;
} }
.collapsedSearchModal {
position: fixed;
inset: 0;
z-index: 35;
display: flex;
justify-content: center;
align-items: flex-start;
padding: 88px 20px 20px;
background: rgba(0, 0, 0, 0.44);
backdrop-filter: blur(6px);
}
.collapsedSearchBar {
width: min(520px, calc(100vw - 32px));
display: flex;
align-items: center;
gap: 14px;
padding: 18px 22px;
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(26, 26, 26, 0.96);
box-shadow: 0 28px 60px rgba(0, 0, 0, 0.34);
}
.collapsedSearchBar__icon {
width: 28px;
height: 28px;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
}
.collapsedSearchBar__icon img {
width: 28px;
height: 28px;
display: block;
filter: brightness(0) saturate(100%) invert(94%) sepia(6%) saturate(207%) hue-rotate(186deg) brightness(96%) contrast(92%);
}
.collapsedSearchBar__input {
min-width: 0;
flex: 1;
border: 0;
background: transparent;
color: rgba(255, 255, 255, 0.92);
font-size: 18px;
font-weight: 700;
outline: none;
}
.collapsedSearchBar__input::placeholder {
color: rgba(255, 255, 255, 0.46);
}
.localRightRailRoot { .localRightRailRoot {
min-height: auto; min-height: auto;
@@ -1007,7 +1058,6 @@ function submitGlobalSearch() {
@media (max-width: 860px) { @media (max-width: 860px) {
.appShell { .appShell {
grid-template-columns: 1fr; grid-template-columns: 1fr;
height: auto;
min-height: 100dvh; min-height: 100dvh;
} }
@@ -1091,10 +1141,11 @@ function submitGlobalSearch() {
} }
.collapsedSearchModal { .collapsedSearchModal {
padding-top: 72px; padding: 72px 16px 16px;
} }
.collapsedSearchBar { .collapsedSearchBar {
width: min(100%, calc(100vw - 24px));
padding: 16px 18px; padding: 16px 18px;
border-radius: 20px; border-radius: 20px;
} }

View File

@@ -82,7 +82,7 @@ function thumbUrl(g) {
</section> </section>
<div v-if="error" class="error">{{ error }}</div> <div v-if="error" class="error">{{ error }}</div>
<section v-if="games.length" class="libraryGrid"> <TransitionGroup v-if="games.length" name="libraryCard" tag="section" class="libraryGrid">
<article v-for="g in games" :key="g.id" class="libraryCard"> <article v-for="g in games" :key="g.id" class="libraryCard">
<button <button
class="libraryCard__favorite" class="libraryCard__favorite"
@@ -104,7 +104,7 @@ function thumbUrl(g) {
</div> </div>
</button> </button>
</article> </article>
</section> </TransitionGroup>
<div v-else class="libraryEmpty">{{ query ? '검색어에 맞는 게임 템플릿이 없어요.' : '표시할 게임 템플릿이 없어요.' }}</div> <div v-else class="libraryEmpty">{{ query ? '검색어에 맞는 게임 템플릿이 없어요.' : '표시할 게임 템플릿이 없어요.' }}</div>
</template> </template>
@@ -139,6 +139,7 @@ function thumbUrl(g) {
gap: 12px; gap: 12px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
transition: transform 0.16s ease, background 0.16s ease; transition: transform 0.16s ease, background 0.16s ease;
will-change: transform, opacity;
} }
.libraryCard:hover { .libraryCard:hover {
background: rgba(70, 70, 70, 0.96); background: rgba(70, 70, 70, 0.96);
@@ -156,8 +157,8 @@ function thumbUrl(g) {
} }
.libraryCard__favorite { .libraryCard__favorite {
position: absolute; position: absolute;
top: 12px; bottom: 24px;
right: 12px; right: 14px;
width: 34px; width: 34px;
height: 34px; height: 34px;
border-radius: 999px; border-radius: 999px;
@@ -206,6 +207,24 @@ function thumbUrl(g) {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.libraryCard-move,
.libraryCard-enter-active,
.libraryCard-leave-active {
transition: transform 280ms ease, opacity 220ms ease;
}
.libraryCard-enter-from,
.libraryCard-leave-to {
opacity: 0;
transform: translateY(10px) scale(0.985);
}
.libraryCard-leave-active {
position: absolute;
width: calc(100% - 0px);
pointer-events: none;
}
.libraryEmpty { .libraryEmpty {
padding: 20px 0; padding: 20px 0;
color: rgba(255, 255, 255, 0.62); color: rgba(255, 255, 255, 0.62);

View File

@@ -54,18 +54,6 @@ function openList(t) {
router.push(`/editor/${t.gameId}/${t.id}`) router.push(`/editor/${t.gameId}/${t.id}`)
} }
async function removeList(t) {
error.value = ''
try {
const ok = window.confirm(`"${t.title}" 티어표를 삭제할까요?`)
if (!ok) return
await api.deleteTierList(t.id)
myLists.value = myLists.value.filter((entry) => entry.id !== t.id)
toast.success('티어표를 삭제했어요.')
} catch (e) {
error.value = '티어표 삭제에 실패했어요.'
}
}
</script> </script>
<template> <template>
@@ -101,7 +89,6 @@ async function removeList(t) {
</div> </div>
</div> </div>
</button> </button>
<button class="link link--danger" @click="removeList(t)">삭제</button>
</article> </article>
</div> </div>
</div> </div>
@@ -115,25 +102,17 @@ async function removeList(t) {
border-radius: 0; border-radius: 0;
padding: 0; padding: 0;
} }
.link {
padding: 10px 12px;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.06);
color: rgba(255, 255, 255, 0.92);
cursor: pointer;
font-weight: 800;
}
.empty { .empty {
opacity: 0.75; opacity: 0.75;
} }
.list { .list {
display: grid; display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 18px; gap: 18px;
} }
.boardCard { .boardCard {
display: grid; display: grid;
min-width: 0;
border-radius: 22px; border-radius: 22px;
border: 1px solid rgba(255, 255, 255, 0.16); border: 1px solid rgba(255, 255, 255, 0.16);
background: rgba(62, 62, 62, 0.82); background: rgba(62, 62, 62, 0.82);
@@ -184,30 +163,40 @@ async function removeList(t) {
border-radius: 18px; border-radius: 18px;
} }
.boardCard__title { .boardCard__title {
font-weight: 900; flex: 1 1 auto;
min-width: 0; min-width: 0;
font-weight: 900;
font-size: 18px; font-size: 18px;
white-space: nowrap; line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis;
} }
.boardCard__head { .boardCard__head {
padding: 16px 18px 18px; padding: 16px 18px 18px;
display: grid; display: grid;
gap: 6px; gap: 8px;
min-width: 0;
} }
.boardCard__titleRow, .boardCard__titleRow,
.boardCard__metaRow { .boardCard__metaRow {
display: flex; display: flex;
gap: 10px; gap: 10px;
min-width: 0;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
} }
.boardCard__titleRow {
align-items: flex-start;
}
.boardCard__metaRow { .boardCard__metaRow {
align-items: flex-end; align-items: flex-end;
} }
.boardCard__author { .boardCard__author {
flex: 1 1 auto;
min-width: 0; min-width: 0;
display: inline-flex; display: inline-flex;
gap: 7px; gap: 7px;
@@ -246,21 +235,6 @@ async function removeList(t) {
.boardCard__date { .boardCard__date {
font-size: 10px; font-size: 10px;
} }
.link--danger {
background: rgba(239, 68, 68, 0.14);
border-color: rgba(239, 68, 68, 0.28);
margin: 0 18px 18px;
}
@media (max-width: 1400px) {
.list {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 1024px) {
.list {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 720px) { @media (max-width: 720px) {
.list { .list {
grid-template-columns: 1fr; grid-template-columns: 1fr;

View File

@@ -744,11 +744,11 @@ onUnmounted(() => {
<div class="templateRequestDraft"> <div class="templateRequestDraft">
<label class="templateRequestDraft__field"> <label class="templateRequestDraft__field">
<span class="templateRequestDraft__label">요청 제목</span> <span class="templateRequestDraft__label">요청 제목</span>
<input v-model="templateRequestDraftTitle" class="input" maxlength="80" placeholder="예: 템플릿 등록 요청" /> <input v-model="templateRequestDraftTitle" class="templateRequestDraft__input" maxlength="80" placeholder="예: 템플릿 등록 요청" />
</label> </label>
<label class="templateRequestDraft__field"> <label class="templateRequestDraft__field">
<span class="templateRequestDraft__label">요청 설명</span> <span class="templateRequestDraft__label">요청 설명</span>
<textarea v-model="templateRequestDraftDescription" class="textarea templateRequestDraft__textarea" maxlength="240" placeholder="예: 여름 이벤트 한정 캐릭터 추가용으로 신규 템플릿이 필요합니다." /> <textarea v-model="templateRequestDraftDescription" class="templateRequestDraft__input templateRequestDraft__textarea" maxlength="240" placeholder="예: 여름 이벤트 한정 캐릭터 추가용으로 신규 템플릿이 필요합니다." />
</label> </label>
</div> </div>
<div class="modalCard__actions"> <div class="modalCard__actions">
@@ -773,11 +773,11 @@ onUnmounted(() => {
<div class="templateRequestDraft"> <div class="templateRequestDraft">
<label class="templateRequestDraft__field"> <label class="templateRequestDraft__field">
<span class="templateRequestDraft__label">요청 제목</span> <span class="templateRequestDraft__label">요청 제목</span>
<input v-model="templateRequestDraftTitle" class="input" maxlength="80" placeholder="예: 템플릿 업데이트 요청" /> <input v-model="templateRequestDraftTitle" class="templateRequestDraft__input" maxlength="80" placeholder="예: 템플릿 업데이트 요청" />
</label> </label>
<label class="templateRequestDraft__field"> <label class="templateRequestDraft__field">
<span class="templateRequestDraft__label">요청 설명</span> <span class="templateRequestDraft__label">요청 설명</span>
<textarea v-model="templateRequestDraftDescription" class="textarea templateRequestDraft__textarea" maxlength="240" placeholder="예: 여름 이벤트 한정 캐릭터 추가" /> <textarea v-model="templateRequestDraftDescription" class="templateRequestDraft__input templateRequestDraft__textarea" maxlength="240" placeholder="예: 여름 이벤트 한정 캐릭터 추가" />
</label> </label>
</div> </div>
<div class="modalCard__actions"> <div class="modalCard__actions">
@@ -990,13 +990,15 @@ onUnmounted(() => {
</div> </div>
<div class="editorSidebar__section editorSidebar__section--footer"> <div class="editorSidebar__section editorSidebar__section--footer">
<label class="toggle" :class="{ 'toggle--disabled': !canEdit }"> <label class="toggleSwitch" :class="{ 'toggleSwitch--disabled': !canEdit }">
<input v-model="isPublic" type="checkbox" :disabled="!canEdit" /> <input v-model="isPublic" type="checkbox" :disabled="!canEdit" />
<span>공개</span> <span class="toggleSwitch__label">공개</span>
<span class="toggleSwitch__track"><span class="toggleSwitch__thumb"></span></span>
</label> </label>
<label class="toggle" :class="{ 'toggle--disabled': !canEdit }"> <label class="toggleSwitch" :class="{ 'toggleSwitch--disabled': !canEdit }">
<input v-model="showCharacterNames" type="checkbox" :disabled="!canEdit" /> <input v-model="showCharacterNames" type="checkbox" :disabled="!canEdit" />
<span>캐릭터 이름 표시</span> <span class="toggleSwitch__label">캐릭터 이름 표시</span>
<span class="toggleSwitch__track"><span class="toggleSwitch__thumb"></span></span>
</label> </label>
<div class="editorSidebar__actionGrid"> <div class="editorSidebar__actionGrid">
<button class="btn btn--ghost editorSidebar__button" @click="downloadImage">이미지 다운로드</button> <button class="btn btn--ghost editorSidebar__button" @click="downloadImage">이미지 다운로드</button>
@@ -1132,23 +1134,56 @@ onUnmounted(() => {
display: inline-flex; display: inline-flex;
position: relative; position: relative;
} }
.toggle { .toggleSwitch {
display: inline-flex; display: inline-flex;
gap: 8px;
align-items: center; align-items: center;
padding: 8px 10px; justify-content: space-between;
border-radius: 12px; gap: 12px;
border: 1px solid rgba(255, 255, 255, 0.12); padding: 10px 12px;
background: rgba(0, 0, 0, 0.12); border-radius: 14px;
font-weight: 800; border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.03);
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
} }
.toggle input { .toggleSwitch input {
width: 16px; position: absolute;
height: 16px; opacity: 0;
pointer-events: none;
} }
.toggle--disabled { .toggleSwitch__track {
position: relative;
width: 42px;
height: 24px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.16);
border: 1px solid rgba(255, 255, 255, 0.12);
transition: background 180ms ease, border-color 180ms ease;
flex: 0 0 auto;
}
.toggleSwitch__thumb {
position: absolute;
top: 2px;
left: 2px;
width: 18px;
height: 18px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.24);
transition: transform 180ms ease;
}
.toggleSwitch__label {
font-weight: 800;
color: rgba(255, 255, 255, 0.9);
}
.toggleSwitch input:checked ~ .toggleSwitch__track {
background: rgba(96, 165, 250, 0.34);
border-color: rgba(96, 165, 250, 0.42);
}
.toggleSwitch input:checked ~ .toggleSwitch__track .toggleSwitch__thumb {
transform: translateX(18px);
}
.toggleSwitch--disabled {
opacity: 0.55; opacity: 0.55;
pointer-events: none; pointer-events: none;
} }
@@ -1305,6 +1340,25 @@ onUnmounted(() => {
font-size: 12px; font-size: 12px;
color: rgba(255, 255, 255, 0.64); color: rgba(255, 255, 255, 0.64);
} }
.templateRequestDraft__input {
width: 100%;
padding: 14px 0;
border: 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
background: transparent;
color: rgba(255, 255, 255, 0.94);
outline: none;
font-size: 18px;
line-height: 1.5;
letter-spacing: -0.02em;
resize: none;
}
.templateRequestDraft__input:focus {
border-bottom-color: rgba(96, 165, 250, 0.9);
}
.templateRequestDraft__input::placeholder {
color: rgba(255, 255, 255, 0.34);
}
.templateRequestDraft__textarea { .templateRequestDraft__textarea {
min-height: 92px; min-height: 92px;
resize: vertical; resize: vertical;