fix(redesign): 카드 4:3 비율·검색바 정렬·다크 고스트버튼 가시성 외 13건

직전 리디자인에서 미해결된 2건(이미지#2 카드 비율, 이미지#3 필터 초기화
가시성)을 다축 UI/UX 평가로 13건 결함으로 확장해 일괄 수정.

비율/레이아웃:
- .card__media aspect-ratio 4/5→4/3 + .card__img object-position:center
  (가로형 웹게임 스크린샷 cover 크롭 ~55%→~25% 완화)
- .search-section 풀폭화 + .search-stack 컬럼 wrapper 신설로 검색행과
  상세검색 패널 우측 끝선 정렬(검색바 "혼자 짧음" 해소)
- .card-grid 고정 N열 → auto-fill(minmax 11rem)로 소량 카드 좌측쏠림 완화
- recruit .recruit-hero__titles max-width·검색 입력 max-width 48rem로 폭 균형

가시성/대비:
- .btn-ghost color: var(--color-on-accent)→var(--color-text)
  (다크 #3A2E15 on #222018 ≈1.1:1 → ≈13:1, WCAG AA 충족)
- 다크 --color-border 0.08→0.20(비텍스트 3:1), .sort-label 대비·위계 보강
- .btn:disabled 하드코딩색 → 토큰화

일관성/위계:
- recruit eyebrow mono/uppercase 통일, 빈상태 시 히어로 CTA 보조강등
  (화면당 골드 CTA 1개), .empty-state 패딩 clamp화

런타임 스모크: bibimbap-app 재기동 후 / + /recruit 200,
라이트/다크 양 화면 육안 검증 PASS. errer.jsp(JSTL) 무관·현재 200.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0162BaZnrbiYgWc9JJMZ9Czb
This commit is contained in:
이정수 2026-06-30 18:24:56 +09:00
parent cf1e7cea62
commit 9489365fa2
5 changed files with 163 additions and 43 deletions

View File

@ -0,0 +1,27 @@
---
phase: implementation
agent: implementation-advisor
agent_version: 1
generated_at: 2026-06-30T17:50:23+09:00
---
# 파일 소유권 맵
| 파일 | 담당 | 변경 유형 | 관련 fix | 의존 |
|---|---|---|---|---|
| src/main/webapp/WEB-INF/views/index.jsp | advisor 직접 | modify | #1 #3 #4 #13 | - |
| src/main/webapp/css/bibimbap.css | advisor 직접 | modify | #2 #6 #7 #11 #12 | - |
| src/main/webapp/WEB-INF/views/recruit-list.jsp | advisor 직접 | modify | #5 #8 #9 #10 | - |
| src/main/webapp/WEB-INF/views/fragments/recruit-empty.jspf | (변경 없음) | none | #9 (이미 충족) | - |
## planned_workers vs actual
- planned_workers(code-writer): 3 (파일당 1)
- actual_workers: 0 (advisor 직접)
- 전환 근거: 편집 파일 3개 < 8 임계, 변경 추정 < 50줄, 전부 소규모 CSS/속성 조정으로
병렬화 이득 없음 + JSP UTF-8 보존 사고 이력으로 advisor 직접 정밀 편집이 더 안전.
- recruit-empty.jspf 는 이미 .btn-primary(골드) + .btn-ghost(보조) 구조라 #9 충족 → 무편집(인코딩 보존).
## 충돌 방지 불변식
- 각 파일은 단일 편집 주체(advisor)만 접근 → 충돌 없음.
- 토큰 계열 분리: index.jsp/recruit-list.jsp 로컬 토큰(--accent/--text/--border 등),
bibimbap.css 는 --color-* 만. #2 .btn-ghost 는 bibimbap.css 소속 → var(--color-text) 정확.

View File

@ -0,0 +1,79 @@
---
schema_version: 2
sid: 20260630-175023
started_at: 2026-06-30T17:50:23
user_request: |
/frontend-design 두 시각 버그 수정 (이전 작업서 미해결):
- Image #2 (메인 게임 허브): 게임 카드 썸네일 "비율" 문제
- Image #3 (팀원 모집 빈 상태): "가시성" 떨어지는 케이스
Invocations: []
Summary: ''
Decisions: []
user_signals:
positive: []
negative:
- quote: "이전 작업에서 안고쳐졌어"
structural: false
note: "직전 리디자인 세션이 두 케이스 미해결. 발원 단계 재검 필요(§2.6)."
regression: ''
graph_refresh: ''
open_items: []
verified_by_me: []
needs_user_verification: []
---
# Advisor Invocation Decision Log
# 각 advisor 호출/스킵 판단 즉시 1줄 append
- advisor: requirements/design/implementation
decision: skip
rationale: '2건 모두 국소 CSS 수정(토큰 오용·종횡비 1값). 이슈2 원인 확정, 이슈1 결정축 단일. orchestrator 직접+plan게이트(§1 마이크로 예외, §5.0).'
checked_at: 2026-06-30T17:52
- advisor: verification
decision: call(self)
rationale: 'CSS 변경 → 런타임 스모크 의무(직전 교훈). 다크모드 대비·카드 비율 육안 확인.'
checked_at: 2026-06-30T17:52
- advisor: implementation
decision: call
rationale: '결정 후 범위=전체13건+카드4:3, 4개 파일(index.jsp/bibimbap.css/recruit-list.jsp/recruit-empty.jspf). 다중파일+일부 마크업변경(#4#9#10) → 파일소유권 분할 필요. 종합 산출 concrete_change 확정적.'
checked_at: 2026-06-30T18:00
---
## Summary
사용자 지적 2건(이미지#2 비율 / 이미지#3 가시성, 직전 세션 미해결)을 다축 멀티에이전트 평가로 13건 결함으로 확장 → 카드 종횡비 4:3 + 전체 13건 적용. 런타임 스모크 라이트/다크 양 화면 PASS.
## Invocations
- cavecrew-investigator: 두 화면 JSP/CSS 위치 file:line 확정
- Workflow(ui-multiaxis-eval, 6 agents): 관찰1 → 다축평가4(레이아웃비율·카드종횡비·대비가시성·타이포간격) → 종합1 → 13 fixes + 1 open_decision
- implementation-advisor: 13건+4:3 구현(3파일, recruit-empty.jspf 무편집)
- (self) verification: 정적 L1 + 런타임 L2 스모크
## Decisions
- 카드 종횡비 **4:3** (사용자 선택; 16:9/4:5유지 대안 제시)
- 적용 범위 **전체 13건** (사용자 선택)
- #4 픽셀 margin 대신 `.search-stack` 컬럼 wrapper (advisor 조정 — 버튼 텍스트 가변 대응 결정적)
- #8 공용 클래스 대신 로컬 시각스펙 일치 (index/bibimbap 토큰계열 분리 제약 + font-mono 토큰 부재)
## verified_by_me
- L1 정적: index.jsp div 18/18·recruit 6/6 균형, scriptlet 균형, U+FFFD 0, stray taglib 0(§293 적신호 없음)
- L2 런타임 스모크(PASS): bibimbap-app(mvn spring-boot:run) 재기동 → GET / + /recruit = 200 → 브라우저 육안 라이트+다크 양 화면.
- 이미지#2(비율): 검색바 풀폭 정렬(#1), 카드 4:3(#13 object-position) — 해소 확인
- 이미지#3(가시성): 필터 초기화 흰글자+보더 명확(#2, zoom 확정) — 해소 확인
- 부수: #6 정렬칩 보더·#7 정렬라벨·#8 eyebrow 통일·#9 빈상태 CTA 강등·#10 히어로폭·#11 패널패딩·푸터 비잘림 육안 확인
## needs_user_verification
- 로그인 상태 1회 스모크: 본 검증은 로그아웃 상태만. (a) 메인 '신규 게임 개시' 버튼 노출 시 #4 search-stack 우측끝선 정렬, (b) 모집글 ≥1건 시 #9 히어로 골드 CTA 유지.
- 실게임 가로 썸네일 업로드 시 4:3 cover 크롭 인상 최종 확인(더미는 폴백 로고만 노출).
## regression
- 발원 단계: 직전 리디자인 세션(cf1e7ce 등)이 두 케이스 미해결. 본 세션이 다축 재검으로 표면화+수정. 토큰 오용(.btn-ghost on-accent) = 골드버튼 토큰을 고스트에 재사용한 설계 결함 → --color-text 로 정정.
## graph_refresh
partial-stale — 내 변경(src view/css)은 include/import/route 위상 0델타 = src fresh. partial 사유는 직전 세션들의 docs/changes 노드 2건 신규(pre-existing) + 직전 graphify 미반영(index.md "재생성 요청 중"이나 source_commit 360078a 고정). 내 세션 구조 무영향 → src 재생성 불요. docs 노드 backlog 는 open_items 로 이월(다음 구조변경 시 일괄 /graphify full).
## open_items
- [pre-existing] docs/graph 재생성 backlog: 직전 세션 docs/changes 노드 미반영 + source_commit stale(360078a→cf1e7ce). 다음 구조변경 작업 시 /graphify full + index.md frontmatter/Scopes 표 갱신. (본 세션 산출 아님)
- [untracked, 타세션] .atp/work-session/{20260630-103443,105459,143405,160000,170024}/ + graphify-out/ : 직전 세션들 미커밋 잔여. 본 작업 단위 외 — 미관여.
- [stale-log] 07:35Z JasperException(/error JSTL) = 내 재기동(09:19Z) 이전 옛 라인, errer.jsp 현재 clean, /error 현재 200. 회귀 아님.

View File

@ -171,6 +171,7 @@
} }
.home-action-button { .home-action-button {
flex-shrink: 0; flex-shrink: 0;
align-self: flex-start;
min-height: 3rem; min-height: 3rem;
padding: 0 0.875rem 0 0.625rem; padding: 0 0.875rem 0 0.625rem;
border: 1px solid rgba(232, 165, 75, 0.32); border: 1px solid rgba(232, 165, 75, 0.32);
@ -222,8 +223,15 @@
/* 검색 */ /* 검색 */
.search-section { .search-section {
width: min(100%, 48rem); width: 100%;
margin: 0 auto 1.25rem; margin: 0 0 1.25rem;
}
/* 검색 폼 + 상세검색 패널을 한 컬럼으로 묶어 우측 끝선 정렬(#4) */
.search-stack {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
} }
.search-form { .search-form {
flex: 1; flex: 1;
@ -506,10 +514,10 @@
transform: scale(0.98); transform: scale(0.98);
} }
/* 카드 그리드: 모바일 2열 → 태블릿 3열 → 데스크톱 4~5열 */ /* 카드 그리드: 콘텐츠 적응형(소량 카드 좌측 쏠림/우측 거대공백 완화) */
.card-grid { .card-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
gap: 0.625rem; gap: 0.625rem;
justify-content: center; justify-content: center;
} }
@ -520,20 +528,9 @@
} }
@media (min-width: 640px) { @media (min-width: 640px) {
.card-grid { .card-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1rem; gap: 1rem;
} }
} }
@media (min-width: 900px) {
.card-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
@media (min-width: 1200px) {
.card-grid {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
}
a.card { a.card {
display: flex; display: flex;
@ -560,11 +557,11 @@
outline: 2px solid var(--accent); outline: 2px solid var(--accent);
outline-offset: 2px; outline-offset: 2px;
} }
/* 가로:세로 = 4:5 (포스터/썸네일에 흔한 비율, 3:5보다 덜 길쭉해 모바일 그리드에 균형 있음) */ /* 가로:세로 = 4:3 (가로형 썸네일 — 카드 높이 축소로 그리드 균형·스캔성 개선) */
.card__media { .card__media {
position: relative; position: relative;
width: 100%; width: 100%;
aspect-ratio: 4 / 5; aspect-ratio: 4 / 3;
background: var(--card-media-2); background: var(--card-media-2);
} }
.card__index { .card__index {
@ -591,6 +588,7 @@
height: 100%; height: 100%;
display: block; display: block;
object-fit: cover; object-fit: cover;
object-position: center;
} }
/* 이미지 없음: 로고만 중앙 */ /* 이미지 없음: 로고만 중앙 */
.card__media-empty { .card__media-empty {
@ -605,7 +603,7 @@
.card__logo-fallback { .card__logo-fallback {
width: min(52%, 7.5rem); width: min(52%, 7.5rem);
height: auto; height: auto;
max-height: 42%; max-height: 50%;
object-fit: contain; object-fit: contain;
opacity: 0.88; opacity: 0.88;
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.12)); filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.12));
@ -768,7 +766,11 @@
<h1 class="section-title" style="font-size:1.875rem">직접 만든 인디 게임을 올리고, 함께 즐기세요</h1> <h1 class="section-title" style="font-size:1.875rem">직접 만든 인디 게임을 올리고, 함께 즐기세요</h1>
<p class="section-desc">유니티 개발자들이 만든 웹게임을 플레이하고 리뷰를 남길 수 있어요.</p> <p class="section-desc">유니티 개발자들이 만든 웹게임을 플레이하고 리뷰를 남길 수 있어요.</p>
<section class="search-section" aria-label="게임·제작자 검색"> <section class="search-section" aria-label="게임·제작자 검색">
<%
boolean advOpen = !searchKeyword.isBlank() || !searchCreator.isBlank() || !"latest".equals(searchSort) || !selectedTagSlugs.isEmpty();
%>
<div class="home-toolbar"> <div class="home-toolbar">
<div class="search-stack">
<form class="search-form" role="search" action="<%= ctx %>/" method="get"> <form class="search-form" role="search" action="<%= ctx %>/" method="get">
<div class="search-form__field"> <div class="search-form__field">
<label class="search-form__label" for="q">게임·제작자 검색</label> <label class="search-form__label" for="q">게임·제작자 검색</label>
@ -776,21 +778,6 @@
</div> </div>
<button class="search-form__submit" type="submit">검색</button> <button class="search-form__submit" type="submit">검색</button>
</form> </form>
<% if (loggedIn) { %>
<a class="home-action-button" href="<%= ctx %>/game/new">
<span class="home-action-button__icon" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 5v14"/>
<path d="M5 12h14"/>
</svg>
</span>
<span>신규 게임 개시</span>
</a>
<% } %>
</div>
<%
boolean advOpen = !searchKeyword.isBlank() || !searchCreator.isBlank() || !"latest".equals(searchSort) || !selectedTagSlugs.isEmpty();
%>
<details class="advanced-search"<%= advOpen ? " open" : "" %>> <details class="advanced-search"<%= advOpen ? " open" : "" %>>
<summary class="advanced-search__summary">상세 검색</summary> <summary class="advanced-search__summary">상세 검색</summary>
<form class="advanced-search-form" role="search" action="<%= ctx %>/games/search" method="get"> <form class="advanced-search-form" role="search" action="<%= ctx %>/games/search" method="get">
@ -851,6 +838,19 @@
</div> </div>
<div class="search-history__chips" id="search-history-chips" role="list"></div> <div class="search-history__chips" id="search-history-chips" role="list"></div>
</div> </div>
</div><!-- /.search-stack -->
<% if (loggedIn) { %>
<a class="home-action-button" href="<%= ctx %>/game/new">
<span class="home-action-button__icon" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 5v14"/>
<path d="M5 12h14"/>
</svg>
</span>
<span>신규 게임 개시</span>
</a>
<% } %>
</div><!-- /.home-toolbar -->
</section> </section>
<%-- 정렬 칩 --%> <%-- 정렬 칩 --%>

View File

@ -60,11 +60,17 @@
gap: 1rem; gap: 1rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.recruit-hero__titles {
max-width: 40rem;
}
.recruit-hero__eyebrow { .recruit-hero__eyebrow {
margin: 0 0 0.35rem; margin: 0 0 0.35rem;
color: var(--accent); color: var(--accent);
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 900; font-weight: 600;
letter-spacing: 0.16em;
text-transform: uppercase;
} }
.recruit-hero h1 { .recruit-hero h1 {
margin: 0; margin: 0;
@ -92,6 +98,13 @@
text-decoration: none; text-decoration: none;
white-space: nowrap; white-space: nowrap;
} }
/* 빈 상태(목록 0건)일 때 히어로 CTA 를 보조 스타일로 강등 — 화면당 골드 채움 CTA 1개(#9) */
.recruit-write--ghost {
background: transparent;
border: 1px solid var(--border);
color: var(--text);
font-weight: 700;
}
.recruit-toolbar { .recruit-toolbar {
margin-bottom: 1.25rem; margin-bottom: 1.25rem;
display: grid; display: grid;
@ -100,6 +113,7 @@
.recruit-search { .recruit-search {
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem;
max-width: 48rem;
} }
.recruit-search input { .recruit-search input {
width: 100%; width: 100%;
@ -248,12 +262,12 @@
<jsp:include page="/WEB-INF/views/header.jsp"/> <jsp:include page="/WEB-INF/views/header.jsp"/>
<main class="recruit-page"> <main class="recruit-page">
<section class="recruit-hero" aria-labelledby="recruit-title"> <section class="recruit-hero" aria-labelledby="recruit-title">
<div> <div class="recruit-hero__titles">
<p class="recruit-hero__eyebrow">INDIE GAME TEAM</p> <p class="recruit-hero__eyebrow">INDIE GAME TEAM</p>
<h1 id="recruit-title">함께 만들 팀원을 찾는 공간</h1> <h1 id="recruit-title">함께 만들 팀원을 찾는 공간</h1>
<p>기획, 아트, 프로그래머가 작은 프로젝트부터 출시 목표 팀까지 가볍게 만나는 게시판입니다.</p> <p>기획, 아트, 프로그래머가 작은 프로젝트부터 출시 목표 팀까지 가볍게 만나는 게시판입니다.</p>
</div> </div>
<a class="recruit-write" href="<%= ctx %>/recruit/new">모집글 작성</a> <a class="recruit-write<%= recruitPosts.isEmpty() ? " recruit-write--ghost" : "" %>" href="<%= ctx %>/recruit/new">모집글 작성</a>
</section> </section>
<section class="recruit-toolbar" aria-label="모집글 검색 및 필터"> <section class="recruit-toolbar" aria-label="모집글 검색 및 필터">

View File

@ -58,7 +58,7 @@ html[data-theme="dark"] {
--color-text-faint: rgba(245, 240, 232, 0.40); /* muted 보다 더 희미 */ --color-text-faint: rgba(245, 240, 232, 0.40); /* muted 보다 더 희미 */
--color-on-accent: #3A2E15; /* 골든 버튼 위 글자 — 라이트와 동일 */ --color-on-accent: #3A2E15; /* 골든 버튼 위 글자 — 라이트와 동일 */
--color-border: rgba(255, 255, 255, 0.08); /* header --header-border 소폭 강조 */ --color-border: rgba(255, 255, 255, 0.20); /* 비텍스트 대비 3:1 확보(WCAG 1.4.11) */
--color-border-soft: rgba(255, 255, 255, 0.06); /* header --header-border */ --color-border-soft: rgba(255, 255, 255, 0.06); /* header --header-border */
--color-accent: #e8a54b; /* 브랜드 골드 — 라이트와 동일 */ --color-accent: #e8a54b; /* 브랜드 골드 — 라이트와 동일 */
@ -123,7 +123,7 @@ a:focus-visible {
.btn-ghost { .btn-ghost {
background: var(--color-card); background: var(--color-card);
color: var(--color-on-accent); color: var(--color-text);
border-color: var(--color-border); border-color: var(--color-border);
} }
.btn-ghost:hover { background: var(--color-field-bg); } .btn-ghost:hover { background: var(--color-field-bg); }
@ -135,9 +135,9 @@ a:focus-visible {
.btn:disabled, .btn:disabled,
.btn[disabled], .btn[disabled],
button:disabled { button:disabled {
background: #EFE7D7; background: var(--color-field-bg);
color: #B3A88E; color: var(--color-text-faint);
border-color: #EFE7D7; border-color: var(--color-border);
cursor: not-allowed; cursor: not-allowed;
} }
@ -266,7 +266,7 @@ button:disabled {
background: var(--color-card); background: var(--color-card);
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: var(--radius-xl); border-radius: var(--radius-xl);
padding: 64px 32px; padding: clamp(40px, 6vw, 64px) 32px;
text-align: center; text-align: center;
} }
.empty-state svg { display: block; margin: 0 auto; } .empty-state svg { display: block; margin: 0 auto; }
@ -318,7 +318,7 @@ button:disabled {
.search-row { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 20px; } .search-row { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 20px; }
.search-row .input { flex: 1; min-width: 240px; } .search-row .input { flex: 1; min-width: 240px; }
.sort-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 16px; } .sort-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
.sort-label { font-size: 13px; color: var(--color-text-faint); margin-right: 4px; } .sort-label { font-size: 12px; font-weight: 700; letter-spacing: .02em; color: var(--color-text-muted); margin-right: 4px; }
.game-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; margin-top: 20px; } .game-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; margin-top: 20px; }
.game-card { background: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-lg); overflow: hidden; text-decoration: none; color: inherit; display: block; } .game-card { background: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius-lg); overflow: hidden; text-decoration: none; color: inherit; display: block; }