diff --git a/docs/history.md b/docs/history.md index 87bc9b5..f6a0ab0 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,10 @@ # 의사결정 이력 +## 2026-03-30 v1.2.7 +- 피그마 감도는 개별 화면보다 공통 셸의 밀도와 아이콘 체계가 먼저 맞아야 하므로, 좌측/우측 레일을 먼저 아이콘형 카드 문법으로 정리하기로 했다. +- 실제 머티리얼 SVG 자산을 받기 전까지는 간단한 선형 SVG 아이콘으로 정보 구조를 먼저 맞추고, 이후 에셋 교체만으로 다듬을 수 있게 하는 편이 안전하다고 판단했다. +- 에디터는 기능은 이미 많은 상태이므로 구조를 더 바꾸기보다 보드, 툴바, 우측 편집 패널의 카드 톤을 공통 셸과 맞추는 방식으로 단계적으로 다듬기로 했다. + ## 2026-03-30 v1.2.6 - 홈, 게임 허브, 내 티어표, 즐겨찾기처럼 카드 중심 화면은 한 번에 같은 카드 문법으로 맞춰야 전체 앱이 하나의 제품처럼 보이므로, 목록 화면을 우선 통일하기로 했다. - 홈 화면은 단순 게임 버튼 모음보다 상태 카드와 CTA가 있는 라이브러리 대시보드 쪽이 피그마 톤에 더 가깝다고 판단했다. diff --git a/docs/map.md b/docs/map.md index 8dfe31e..3da9c24 100644 --- a/docs/map.md +++ b/docs/map.md @@ -42,7 +42,7 @@ ## 공통 레이아웃 - 앱 셸 파일: `frontend/src/App.vue` -- 역할: 좌측 내비게이션, 중앙 워크스페이스, 우측 컨텍스트 패널로 구성된 공통 앱 셸 렌더링, 로그인 상태 반영, 사용자 메뉴, 관리자 메뉴 노출 제어, 전역 우측 상단 토스트 렌더링 +- 역할: 좌측 내비게이션, 중앙 워크스페이스, 우측 컨텍스트 패널로 구성된 공통 앱 셸 렌더링, 로그인 상태 반영, 사용자 메뉴, 관리자 메뉴 노출 제어, 선형 SVG 아이콘 기반 레일 UI, 전역 우측 상단 토스트 렌더링 - 세부: 좌측 패널은 `248px`, 우측 패널은 `320px` 기준 폭을 사용하며, 상단 토글 버튼으로 우측 패널을 접고 펼칠 수 있다. ## 백엔드 진입점 diff --git a/docs/spec.md b/docs/spec.md index d81cbe9..b046f76 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -12,6 +12,7 @@ - 프런트 앱 셸은 `좌측 내비게이션 / 중앙 워크스페이스 / 우측 컨텍스트 패널` 3단 구조로 재정의되었고, preview 모드에서는 이 셸을 숨기고 콘텐츠만 렌더링한다. - 좌측 패널은 `248px`, 우측 패널은 `320px` 기준 폭을 사용하며, 우측 패널은 상단 토글 버튼으로 접고 펼칠 수 있다. - 비로그인 상태의 로그인 유도는 좌측 하단 버튼으로만 노출하고, 좌측 상단 사용자 카드 영역에는 별도 게스트 안내 카드를 렌더링하지 않는다. +- 공통 셸의 좌측 내비, 우측 패널, 빠른 점프 버튼은 간단한 선형 SVG 아이콘과 두꺼운 카드형 버튼 문법을 공유한다. ## 데이터 저장 구조 - 메인 데이터베이스: MariaDB `tier_cursor` (기본값) diff --git a/docs/todo.md b/docs/todo.md index 81520b2..646dbde 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -3,6 +3,7 @@ ## 즉시 확인 필요 - 피그마 기반 리디자인은 현재 공통 셸과 카드 목록 화면, 포커스 화면 안정화까지만 반영된 상태이므로, 에디터/관리자 우측 옵션 패널을 시안 구조에 맞게 실제 기능 패널로 이관하는 작업이 남아 있다. - 홈/게임 허브/내 티어표/즐겨찾기 카드 문법은 어느 정도 통일됐지만, 아직 실제 SVG 아이콘, 미세 간격, hover/selection 상태 같은 디테일은 더 다듬을 필요가 있다. +- 현재 공통 셸에는 임시 선형 SVG 아이콘을 사용하므로, 최종 머티리얼 아이콘 에셋을 받으면 교체하고 아이콘 크기/정렬을 다시 미세 조정할 필요가 있다. - 공통 우측 패널의 토글과 독립 컬럼 구조는 반영되었지만, 현재는 안내 카드 중심이므로 실제 화면별 기능 컨트롤을 이 패널로 옮기는 작업이 남아 있다. - 티어표 편집 화면과 관리자 화면 모두 로컬 우측 패널 구조로 옮겼지만, 아직 세부 카드 밀도와 아이콘/모션 디테일은 피그마 시안 수준으로 더 다듬을 필요가 있다. - 머티리얼 아이콘 SVG는 아직 임시 문자/배지 스타일로 대체된 부분이 있으므로, 최종 아이콘 에셋을 받아 반영하는 작업이 필요하다. diff --git a/docs/update.md b/docs/update.md index 1250ef0..1649b68 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,10 @@ # 업데이트 로그 +## 2026-03-30 v1.2.7 +- **공통 셸 아이콘형 정리**: 좌측 내비와 우측 보조 패널의 임시 문자 배지를 간단한 SVG 아이콘형으로 바꾸고, 버튼/카드 라운드와 밀도를 통일 +- **좌측 레일 정보 밀도 개선**: 사용자 카드, 빠른 검색, 내비 버튼, 하단 로그인/관리자 버튼을 더 두꺼운 카드 문법으로 맞춰 피그마 톤에 가까운 레일 형태로 재정리 +- **에디터 패널 감도 보정**: 티어표 편집 화면의 보드, 보드 툴바, 우측 편집 패널, 아이템 풀/드롭존 카드의 배경·경계·라운드를 함께 정리해 공통 셸과 시각 언어를 맞춤 + ## 2026-03-30 v1.2.6 - **목록형 화면 카드 문법 통일**: 홈, 게임 허브, 내 티어표, 즐겨찾기 화면의 카드형 목록을 동일한 썸네일/제목/작성자/메타 구조로 정리해 대시보드 톤을 맞춤 - **홈 화면 대시보드 재정렬**: 메인 게임 라이브러리 화면에 상단 상태 카드와 CTA를 추가하고, 게임 카드는 `16:9` 썸네일 + ID 메타를 갖는 라이브러리 카드 형태로 재배치 diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 778c9e4..5138c7f 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -27,21 +27,21 @@ const accountName = computed(() => { const accountEmail = computed(() => (auth.user?.email || '').trim() || '로그인 후 개인 메뉴를 사용할 수 있어요.') const leftNavItems = computed(() => { const items = [ - { key: 'home', label: 'Games', path: '/', initials: 'GM' }, - { key: 'me', label: '내 리스트', path: '/me', initials: 'ME', requiresAuth: true }, - { key: 'favorites', label: '즐겨찾기', path: '/favorites', initials: 'FV', requiresAuth: true }, - { key: 'profile', label: 'Settings', path: '/profile', initials: 'ST', requiresAuth: true }, + { key: 'home', label: 'Games', path: '/', icon: 'M4 6.5h16M4 12h16M4 17.5h16' }, + { key: 'me', label: '내 리스트', path: '/me', icon: 'M6 5.5h12v12H6z M8.75 8.5h6.5 M8.75 11.75h6.5 M8.75 15h4.5', requiresAuth: true }, + { key: 'favorites', label: '즐겨찾기', path: '/favorites', icon: 'M12 4.75l2.18 4.42 4.88.71-3.53 3.44.83 4.86L12 15.9 7.64 18.18l.83-4.86-3.53-3.44 4.88-.71z', requiresAuth: true }, + { key: 'profile', label: 'Settings', path: '/profile', icon: 'M12 4.75a2.2 2.2 0 0 1 2.08 1.5l.18.56.58.13a2.2 2.2 0 0 1 1.52 2.76l-.17.56.39.46a2.2 2.2 0 0 1 0 2.86l-.39.46.17.56a2.2 2.2 0 0 1-1.52 2.76l-.58.13-.18.56a2.2 2.2 0 0 1-4.16 0l-.18-.56-.58-.13a2.2 2.2 0 0 1-1.52-2.76l.17-.56-.39-.46a2.2 2.2 0 0 1 0-2.86l.39-.46-.17-.56a2.2 2.2 0 0 1 1.52-2.76l.58-.13.18-.56A2.2 2.2 0 0 1 12 4.75z M12 9.35a2.65 2.65 0 1 0 0 5.3 2.65 2.65 0 0 0 0-5.3z', requiresAuth: true }, ] if (isAdmin.value) { - items.push({ key: 'admin', label: 'Admin', path: '/admin', initials: 'AD' }) + items.push({ key: 'admin', label: 'Admin', path: '/admin', icon: 'M5.5 6.25h13v13h-13z M9 9.25h6 M9 12h6 M9 14.75h4.5' }) } return items.filter((item) => !item.requiresAuth || auth.user) }) const routeMeta = computed(() => { if (route.name === 'home') { return { - title: 'Main Title', - subtitle: '게임 선택 및 커스텀 티어표 진입', + title: 'Tier Maker', + subtitle: '게임 템플릿 선택과 커스텀 보드 시작', contextTitle: '빠른 시작', contextText: auth.user ? '커스텀 티어표를 만들거나 원하는 게임을 바로 선택할 수 있어요.' : '로그인하면 커스텀 티어표 생성과 개인 목록 관리가 열립니다.', actionLabel: auth.user ? '커스텀 티어표 만들기' : '로그인하러 가기', @@ -52,8 +52,8 @@ const routeMeta = computed(() => { } if (route.name === 'gameHub') { return { - title: 'Tier Lists', - subtitle: '게임별 공개 티어표 목록', + title: 'Game Boards', + subtitle: '게임별 공개 티어표 탐색', contextTitle: '작성 작업', contextText: auth.user ? '이 게임의 새 티어표를 만들거나 기존 공개 티어표를 확인할 수 있어요.' : '로그인 후 새 티어표를 만들 수 있어요.', actionLabel: auth.user ? '새 티어표 만들기' : '로그인하러 가기', @@ -128,6 +128,14 @@ const favoriteLinks = computed(() => [ ...(auth.user ? [{ label: 'My Lists', path: '/me' }] : []), ]) +function railGlyph(type) { + if (type === 'menu') return 'M4 6.5h16M4 12h16M4 17.5h16' + if (type === 'search') return 'M10.2 6.2a4 4 0 1 1 0 8 4 4 0 0 1 0-8z M13.6 13.6l3.2 3.2' + if (type === 'panel') return 'M5.5 6.5h5v5h-5z M13.5 6.5h5v5h-5z M5.5 13.5h5v5h-5z M13.5 13.5h5v5h-5z' + if (type === 'link') return 'M8 12h8 M12 8l4 4-4 4' + return 'M4 12h16' +} + onMounted(async () => { await auth.refresh() if (typeof window !== 'undefined') { @@ -192,7 +200,9 @@ async function logout() { @@ -375,14 +403,37 @@ async function logout() { background: rgba(255, 255, 255, 0.03); color: rgba(255, 255, 255, 0.72); cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +.ghostIcon svg, +.searchStub__icon svg, +.leftNav__glyph svg, +.contextLink svg { + width: 16px; + height: 16px; + stroke: currentColor; + stroke-width: 1.8; + fill: none; + stroke-linecap: round; + stroke-linejoin: round; +} + +.ghostIcon--iconOnly { + min-width: 32px; + width: 32px; + padding: 0; } .ghostIcon--workspace { - min-width: 118px; - height: 36px; + min-width: 132px; + height: 38px; padding: 0 14px; border-radius: 10px; - background: rgba(255, 255, 255, 0.05); + background: rgba(255, 255, 255, 0.06); color: rgba(255, 255, 255, 0.88); font-size: 12px; font-weight: 800; @@ -390,7 +441,7 @@ async function logout() { .brandBlock { display: grid; - gap: 2px; + gap: 4px; cursor: pointer; } @@ -418,9 +469,9 @@ async function logout() { gap: 12px; align-items: center; padding: 10px; - border-radius: 14px; + border-radius: 16px; border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.03); + background: rgba(255, 255, 255, 0.04); color: inherit; text-align: left; cursor: pointer; @@ -428,8 +479,8 @@ async function logout() { } .appUserCard__avatar { - width: 38px; - height: 38px; + width: 42px; + height: 42px; border-radius: 12px; object-fit: cover; flex: 0 0 auto; @@ -490,17 +541,21 @@ async function logout() { display: flex; align-items: center; gap: 10px; - padding: 10px 12px; - border-radius: 12px; + padding: 11px 12px; + border-radius: 14px; border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.02); + background: rgba(255, 255, 255, 0.03); color: rgba(255, 255, 255, 0.62); cursor: pointer; margin-bottom: 14px; } .searchStub__icon { - font-size: 14px; + width: 18px; + height: 18px; + display: inline-flex; + align-items: center; + justify-content: center; } .leftNav { @@ -511,29 +566,27 @@ async function logout() { .leftNav__item { display: flex; align-items: center; - gap: 10px; - padding: 10px; - border-radius: 12px; + gap: 12px; + padding: 11px 12px; + border-radius: 14px; color: rgba(255, 255, 255, 0.76); text-decoration: none; + transition: background 180ms ease, color 180ms ease, transform 180ms ease; } .leftNav__item--active, .leftNav__item.router-link-active { - background: rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.1); color: rgba(255, 255, 255, 0.96); } .leftNav__glyph { - width: 24px; - height: 24px; - border-radius: 8px; + width: 28px; + height: 28px; + border-radius: 10px; display: grid; place-items: center; background: rgba(255, 255, 255, 0.06); - font-size: 10px; - font-weight: 900; - letter-spacing: 0.06em; flex: 0 0 auto; } @@ -557,6 +610,7 @@ async function logout() { color: rgba(255, 255, 255, 0.7); text-decoration: none; font-size: 14px; + padding: 4px 0; } .favoriteLink__dot { @@ -577,12 +631,13 @@ async function logout() { justify-content: center; align-items: center; padding: 12px 14px; - border-radius: 12px; + border-radius: 14px; border: 1px solid rgba(255, 255, 255, 0.12); - background: rgba(255, 255, 255, 0.04); + background: rgba(255, 255, 255, 0.05); color: rgba(255, 255, 255, 0.92); text-decoration: none; box-sizing: border-box; + font-weight: 800; } .leftRail { @@ -620,7 +675,7 @@ async function logout() { } .workspaceHead__title { - font-size: 28px; + font-size: 30px; font-weight: 900; letter-spacing: -0.04em; } @@ -633,10 +688,10 @@ async function logout() { .workspaceBody { min-height: calc(100vh - 110px); - padding: 18px; + padding: 20px; border-radius: 26px; border: 1px solid rgba(255, 255, 255, 0.08); - background: #2b2b2b; + background: linear-gradient(180deg, #2d2d2d 0%, #2a2a2a 100%); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); } @@ -649,10 +704,10 @@ async function logout() { .contextCard { display: grid; gap: 12px; - padding: 14px; + padding: 16px; border-radius: 18px; border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.02); + background: rgba(255, 255, 255, 0.03); } .contextCard__label { @@ -664,7 +719,7 @@ async function logout() { .contextCard__title { margin: 0; - font-size: 20px; + font-size: 22px; line-height: 1.2; } @@ -686,6 +741,24 @@ async function logout() { cursor: pointer; } +.contextCard--links { + gap: 10px; +} + +.contextLink { + width: 100%; + display: inline-flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 11px 12px; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.03); + color: rgba(255, 255, 255, 0.86); + cursor: pointer; +} + .contextStat { display: flex; justify-content: space-between; diff --git a/frontend/src/views/TierEditorView.vue b/frontend/src/views/TierEditorView.vue index 4c6bdb8..91a1f36 100644 --- a/frontend/src/views/TierEditorView.vue +++ b/frontend/src/views/TierEditorView.vue @@ -1038,10 +1038,11 @@ onUnmounted(() => { } .board { border: 1px solid rgba(255, 255, 255, 0.1); - background: rgba(48, 48, 48, 0.78); - border-radius: 18px; - padding: 18px; + background: linear-gradient(180deg, rgba(55, 55, 55, 0.86), rgba(42, 42, 42, 0.82)); + border-radius: 22px; + padding: 20px; align-self: start; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); } .modalOverlay { position: fixed; @@ -1124,8 +1125,12 @@ onUnmounted(() => { gap: 12px; align-items: center; justify-content: flex-end; - margin-bottom: 14px; + margin-bottom: 16px; flex-wrap: wrap; + padding: 10px 12px; + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.03); } .boardTools__left, .boardTools__right { @@ -1150,8 +1155,8 @@ onUnmounted(() => { .sizePicker__button { margin: 0; min-width: 48px; - padding: 8px 10px; - border-radius: 10px; + padding: 9px 10px; + border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.14); background: rgba(255, 255, 255, 0.05); color: rgba(255, 255, 255, 0.92); @@ -1208,8 +1213,8 @@ onUnmounted(() => { align-items: stretch; } .row__label { - border-radius: 14px; - background: rgba(255, 255, 255, 0.06); + border-radius: 16px; + background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.12); display: flex; gap: 8px; @@ -1234,7 +1239,7 @@ onUnmounted(() => { .groupName { width: 100%; border: 1px solid rgba(255, 255, 255, 0.14); - background: rgba(0, 0, 0, 0.12); + background: rgba(0, 0, 0, 0.18); color: rgba(255, 255, 255, 0.92); border-radius: 10px; padding: 6px 8px; @@ -1264,7 +1269,7 @@ onUnmounted(() => { cursor: not-allowed; } .row__drop { - border-radius: 14px; + border-radius: 16px; background: rgba(0, 0, 0, 0.18); border: 1px solid rgba(255, 255, 255, 0.10); min-height: calc(var(--thumb-size, 80px) + 24px); @@ -1322,36 +1327,44 @@ onUnmounted(() => { } .sidebar { border: 1px solid rgba(255, 255, 255, 0.1); - background: rgba(48, 48, 48, 0.78); - border-radius: 18px; - padding: 12px; + background: linear-gradient(180deg, rgba(52, 52, 52, 0.84), rgba(36, 36, 36, 0.8)); + border-radius: 22px; + padding: 14px; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); } .editorSidebar { display: grid; align-content: start; gap: 14px; padding: 14px 12px; - border-radius: 18px; + border-radius: 22px; border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(18, 18, 18, 0.96); + background: linear-gradient(180deg, rgba(17, 17, 17, 0.96), rgba(12, 12, 12, 0.96)); position: sticky; top: 14px; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); } .editorSidebar__section { display: grid; gap: 10px; + padding: 12px; + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.06); + background: rgba(255, 255, 255, 0.02); } .editorSidebar__label { - font-size: 13px; + font-size: 11px; font-weight: 800; - color: rgba(255, 255, 255, 0.82); + color: rgba(255, 255, 255, 0.52); + text-transform: uppercase; + letter-spacing: 0.12em; } .editorSidebar__input, .editorSidebar__textarea { width: 100%; - border-radius: 12px; + border-radius: 14px; border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.03); + background: rgba(255, 255, 255, 0.04); color: rgba(255, 255, 255, 0.92); padding: 11px 12px; outline: none; @@ -1371,7 +1384,7 @@ onUnmounted(() => { .editorSidebar__thumbFrame { width: 100%; aspect-ratio: 16 / 9; - border-radius: 14px; + border-radius: 16px; overflow: hidden; border: 1px solid rgba(255, 255, 255, 0.08); background: #4c4c4c; @@ -1404,17 +1417,16 @@ onUnmounted(() => { justify-content: space-between; gap: 10px; width: 100%; - padding: 12px 0 0; - border: 0; - border-top: 1px solid rgba(255, 255, 255, 0.08); - background: transparent; + padding: 11px 12px; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.03); color: rgba(255, 255, 255, 0.9); font-weight: 800; cursor: pointer; } .editorSidebar__section--footer { padding-top: 12px; - border-top: 1px solid rgba(255, 255, 255, 0.08); } .editorSidebar__actionGrid { display: grid; @@ -1423,19 +1435,22 @@ onUnmounted(() => { } .sidebar__title { font-weight: 900; - margin-bottom: 6px; + margin-bottom: 8px; + font-size: 18px; + letter-spacing: -0.02em; } .sidebar__hint { opacity: 0.78; font-size: 13px; - margin-bottom: 10px; + margin-bottom: 12px; + line-height: 1.5; } .customItemEditor { margin-top: 12px; padding: 12px; border-radius: 16px; border: 1px solid rgba(255, 255, 255, 0.1); - background: rgba(255, 255, 255, 0.03); + background: rgba(255, 255, 255, 0.04); } .customItemEditor__title { font-weight: 900; @@ -1480,7 +1495,7 @@ onUnmounted(() => { padding: 14px; border-radius: 16px; border: 1px dashed rgba(255, 255, 255, 0.18); - background: rgba(255, 255, 255, 0.03); + background: rgba(255, 255, 255, 0.04); text-align: center; } .dropzone--active { @@ -1506,9 +1521,9 @@ onUnmounted(() => { gap: 10px; align-items: center; padding: 10px; - border-radius: 14px; + border-radius: 16px; border: 1px solid rgba(255, 255, 255, 0.10); - background: rgba(0, 0, 0, 0.16); + background: rgba(0, 0, 0, 0.18); } .poolItem__label { font-weight: 800;