Compare commits

...

3 Commits

6 changed files with 83 additions and 8 deletions

View File

@@ -1,5 +1,14 @@
# 의사결정 이력
## 2026-04-02 v1.3.92
- 왼쪽 레일 활성 메뉴도 로그인 토글과 같은 이동형 배경 문법을 쓰는 편이 앱 전체 인터랙션 언어를 더 일관되게 만든다고 판단했다.
## 2026-04-02 v1.3.91
- 로그인/회원가입 탭은 즉시 배경 교체보다, 선택 배경이 실제로 이동하는 토글 문법이 더 직관적이고 상태 전환이 잘 읽힌다고 판단했다.
## 2026-04-02 v1.3.90
- 경고 수준의 CSS 진단이라도 실제 의미 없는 속성이나 벤더 전용 속성 누락이라면 바로 정리해 두는 편이 이후 유지보수 피로를 줄인다고 판단했다.
## 2026-04-02 v1.3.89
- 더 이상 참조되지 않는 Vite 기본 자산과 레거시 public 아이콘 묶음은 남겨둘수록 혼동만 커지므로, 실제 사용 파일만 남기고 정리하는 편이 맞다고 판단했다.
- 공유용 썸네일은 코드 수정과 별개로 시각 자산 손질이 자주 일어날 수 있으므로, 이번처럼 워크트리에 이미 반영된 최신 이미지 수정본은 함께 릴리스에 포함하는 편이 맞다고 정리했다.

View File

@@ -1,6 +1,9 @@
# 할 일 및 이슈
## 단기 확인
- 왼쪽 레일 활성 배경은 공용 인디케이터가 이동하는 방식으로 바뀌었으므로, 홈/내 티어표/즐겨찾기/설정 전환과 레일 접힘 상태 양쪽에서 위치 보정이 자연스러운지 한 번 더 QA한다.
- 로그인 화면 상단 토글은 이동형 인디케이터로 바뀌었으므로, 데스크톱과 모바일에서 `로그인 / 회원가입` 전환 애니메이션이 어색하지 않고 포커스/클릭 상태도 자연스러운지 한 번 더 QA한다.
- 관리자 카드 설명 줄임은 `line-clamp` 표준 속성까지 함께 선언했으므로, 실제 브라우저별 표시 차이가 없는지 한 번 더 QA한다.
- 사용하지 않는 기본 자산을 정리했으므로, 배포본에서 누락 참조 없이 파비콘/공유 썸네일/좌측 레일 아이콘이 정상 노출되는지 한 번 더 QA한다.
- 공유 썸네일 `og-card`는 이번에 이미지 수정본까지 함께 반영했으므로, 실제 메신저 미리보기에서 최신 그림이 캐시 갱신 후 정상 노출되는지 한 번 더 QA한다.
- 홈페이지 공유 메타와 새 `og-card.png`는 이번에 처음 붙였으므로, 카카오톡/디스코드/슬랙/모바일 브라우저에서 제목·설명·썸네일이 기대대로 보이는지 한 번 더 QA한다.

View File

@@ -1,5 +1,14 @@
# 업데이트 로그
## 2026-04-02 v1.3.92
- 왼쪽 네비게이션의 활성 메뉴 배경은 개별 항목에 즉시 붙는 방식에서, 공용 인디케이터가 현재 메뉴 위치로 미끄러져 이동하는 토글형 인터랙션으로 정리함.
## 2026-04-02 v1.3.91
- 로그인 화면 상단의 `로그인 / 회원가입` 전환은 선택된 버튼 배경이 즉시 바뀌던 방식에서, 뒤쪽 하이라이트가 토글처럼 좌우로 미끄러져 이동하는 인터랙션으로 정리함.
## 2026-04-02 v1.3.90
- 관리자 화면 CSS 경고를 줄이기 위해 `display: block` 요소에 의미 없던 `vertical-align`을 제거하고, `line-clamp` 표준 속성을 함께 선언해 VS Code 진단을 정리함.
## 2026-04-02 v1.3.89
- 현재 코드에서 참조되지 않던 `frontend/public/icons.svg`, `frontend/src/assets/hero.png`, `frontend/src/assets/vite.svg`, `frontend/src/assets/vue.svg`를 삭제해 템플릿 잔재 자산을 정리함.
- 홈페이지 공유용 `og-card.svg`, `og-card.png`는 이번 워크트리에서 직접 수정된 최신 이미지 상태를 그대로 반영해 함께 정리함.

View File

@@ -67,6 +67,7 @@ const leftNavItems = computed(() => {
]
return items.filter((item) => !item.requiresAuth || (authReady.value && auth.user))
})
const activeLeftNavIndex = computed(() => leftNavItems.value.findIndex((item) => isRouteActive(item.path)))
const showRightRailAction = computed(() => false)
const showSettingsGuideButton = computed(() => route.name === 'profile')
const guideSteps = [
@@ -446,7 +447,12 @@ function submitGlobalSearch() {
<input v-model="searchQuery" class="searchStub__input" type="search" :placeholder="leftRailCollapsed ? '' : leftRailSearchPlaceholder" />
</form>
<nav class="leftNav">
<nav
class="leftNav"
:class="{ 'leftNav--hasActive': activeLeftNavIndex >= 0 }"
:style="{ '--left-nav-active-index': String(Math.max(activeLeftNavIndex, 0)) }"
>
<span class="leftNav__indicator" aria-hidden="true"></span>
<RouterLink
v-for="item in leftNavItems"
:key="item.key"
@@ -911,19 +917,45 @@ function submitGlobalSearch() {
}
.leftNav {
--left-nav-gap: 8px;
--left-nav-item-height: 50px;
position: relative;
display: grid;
gap: 8px;
gap: var(--left-nav-gap);
isolation: isolate;
}
.leftNav__indicator {
position: absolute;
top: 0;
left: 0;
right: 0;
height: var(--left-nav-item-height);
border-radius: 14px;
background: var(--theme-surface-soft-3);
transform: translateY(calc(var(--left-nav-active-index, 0) * (var(--left-nav-item-height) + var(--left-nav-gap))));
transition: transform 240ms ease, opacity 200ms ease;
opacity: 0;
z-index: 0;
pointer-events: none;
}
.leftNav--hasActive .leftNav__indicator {
opacity: 1;
}
.leftNav__item {
position: relative;
display: flex;
align-items: center;
min-height: var(--left-nav-item-height);
gap: 12px;
padding: 11px 12px;
border-radius: 14px;
color: var(--theme-text-muted);
text-decoration: none;
transition: background 180ms ease, color 180ms ease, transform 180ms ease;
z-index: 1;
}
.leftNav__label {
@@ -936,7 +968,6 @@ function submitGlobalSearch() {
.leftNav__item--active,
.leftNav__item.router-link-active {
background: var(--theme-surface-soft-3);
color: var(--theme-text-strong);
}
@@ -999,7 +1030,7 @@ function submitGlobalSearch() {
}
.appShell--leftCollapsed .leftNav {
gap: 10px;
--left-nav-gap: 10px;
}
.appShell--leftCollapsed .leftNav__item {

View File

@@ -3891,7 +3891,6 @@ function userAvatarFallback(user) {
display: block;
width: 100%;
line-height: 0;
vertical-align: top;
}
.adminUiScope .templateRequestCard__thumbMeta {
display: grid;
@@ -4145,6 +4144,7 @@ function userAvatarFallback(user) {
color: var(--theme-text-muted);
line-height: 1.5;
display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;

View File

@@ -87,7 +87,8 @@ async function submit() {
</section>
<section v-else class="authScreen">
<div class="authTabs" role="tablist" aria-label="로그인 또는 회원가입">
<div class="authTabs" :class="{ 'authTabs--signup': mode === 'signup' }" role="tablist" aria-label="로그인 또는 회원가입">
<span class="authTabs__indicator" aria-hidden="true"></span>
<button type="button" class="authTabs__button" :class="{ 'authTabs__button--active': mode === 'login' }" @click="mode = 'login'">
로그인
</button>
@@ -159,16 +160,37 @@ async function submit() {
}
.authTabs {
position: relative;
display: inline-flex;
gap: 8px;
gap: 0;
width: fit-content;
padding: 6px;
border-radius: 999px;
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
isolation: isolate;
}
.authTabs__indicator {
position: absolute;
top: 6px;
left: 6px;
width: calc(50% - 6px);
height: calc(100% - 12px);
border-radius: 999px;
background: rgba(76, 133, 245, 0.22);
box-shadow: inset 0 0 0 1px rgba(120, 169, 255, 0.1);
transform: translateX(0);
transition: transform 220ms ease, background-color 220ms ease, box-shadow 220ms ease;
z-index: 0;
}
.authTabs--signup .authTabs__indicator {
transform: translateX(100%);
}
.authTabs__button {
position: relative;
min-width: 112px;
padding: 10px 16px;
border: 0;
@@ -177,10 +199,11 @@ async function submit() {
color: var(--theme-text-muted);
font-weight: 700;
cursor: pointer;
transition: color 180ms ease;
z-index: 1;
}
.authTabs__button--active {
background: rgba(76, 133, 245, 0.22);
color: var(--theme-text-strong);
}